summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorAndrei Pavel <andrei.pavel@qualitance.com>2017-08-17 20:04:29 +0200
committerAndrei Pavel <andrei.pavel@qualitance.com>2017-08-17 20:04:29 +0200
commit529d15326887b3513413567e497118b3db2c24f3 (patch)
tree8b66b262349433802bd52e920bb4783baac57cb3 /src/lib
parentAdded mysql_execute_script (diff)
parent[master] Added ChangeLog 1288 for trac 5315. (diff)
downloadkea-529d15326887b3513413567e497118b3db2c24f3.tar.xz
kea-529d15326887b3513413567e497118b3db2c24f3.zip
Merge branch 'isc-master' into minor-changes
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Makefile.am4
-rw-r--r--src/lib/asiodns/README10
-rw-r--r--src/lib/asiodns/io_fetch.cc10
-rw-r--r--src/lib/asiolink/Makefile.am9
-rw-r--r--src/lib/asiolink/asio_wrapper.h22
-rw-r--r--src/lib/asiolink/interval_timer.cc4
-rw-r--r--src/lib/asiolink/io_acceptor.h132
-rw-r--r--src/lib/asiolink/io_address.h8
-rw-r--r--src/lib/asiolink/io_asio_socket.h3
-rw-r--r--src/lib/asiolink/io_service.cc18
-rw-r--r--src/lib/asiolink/io_service.h6
-rw-r--r--src/lib/asiolink/io_socket.h12
-rw-r--r--src/lib/asiolink/tcp_acceptor.h69
-rw-r--r--src/lib/asiolink/tcp_socket.h64
-rw-r--r--src/lib/asiolink/tests/Makefile.am8
-rw-r--r--src/lib/asiolink/tests/interval_timer_unittest.cc3
-rw-r--r--src/lib/asiolink/tests/tcp_acceptor_unittest.cc442
-rw-r--r--src/lib/asiolink/tests/tcp_socket_unittest.cc27
-rw-r--r--src/lib/asiolink/tests/unix_domain_socket_unittest.cc313
-rw-r--r--src/lib/asiolink/testutils/Makefile.am24
-rw-r--r--src/lib/asiolink/testutils/test_server_unix_socket.cc317
-rw-r--r--src/lib/asiolink/testutils/test_server_unix_socket.h171
-rw-r--r--src/lib/asiolink/unix_domain_socket.cc364
-rw-r--r--src/lib/asiolink/unix_domain_socket.h137
-rw-r--r--src/lib/asiolink/unix_domain_socket_acceptor.h65
-rw-r--r--src/lib/asiolink/unix_domain_socket_endpoint.h50
-rw-r--r--src/lib/cc/Makefile.am13
-rw-r--r--src/lib/cc/cc.dox99
-rw-r--r--src/lib/cc/cfg_to_element.h48
-rw-r--r--src/lib/cc/command_interpreter.cc93
-rw-r--r--src/lib/cc/command_interpreter.h35
-rw-r--r--src/lib/cc/data.cc282
-rw-r--r--src/lib/cc/data.h309
-rw-r--r--src/lib/cc/dhcp_config_error.h73
-rw-r--r--src/lib/cc/json_feed.cc312
-rw-r--r--src/lib/cc/json_feed.h276
-rw-r--r--src/lib/cc/simple_parser.cc213
-rw-r--r--src/lib/cc/simple_parser.h265
-rw-r--r--src/lib/cc/tests/Makefile.am6
-rw-r--r--src/lib/cc/tests/command_interpreter_unittests.cc15
-rw-r--r--src/lib/cc/tests/data_file_unittests.cc6
-rw-r--r--src/lib/cc/tests/data_unittests.cc244
-rw-r--r--src/lib/cc/tests/json_feed_unittests.cc174
-rw-r--r--src/lib/cc/tests/simple_parser_unittest.cc237
-rw-r--r--src/lib/config/Makefile.am8
-rw-r--r--src/lib/config/base_command_mgr.cc143
-rw-r--r--src/lib/config/base_command_mgr.h199
-rw-r--r--src/lib/config/client_connection.cc258
-rw-r--r--src/lib/config/client_connection.h157
-rw-r--r--src/lib/config/command-socket.dox48
-rw-r--r--src/lib/config/command_mgr.cc633
-rw-r--r--src/lib/config/command_mgr.h178
-rw-r--r--src/lib/config/command_socket.cc43
-rw-r--r--src/lib/config/command_socket.h105
-rw-r--r--src/lib/config/command_socket_factory.cc226
-rw-r--r--src/lib/config/command_socket_factory.h39
-rw-r--r--src/lib/config/config_log.h4
-rw-r--r--src/lib/config/config_messages.mes57
-rw-r--r--src/lib/config/documentation.txt2
-rw-r--r--src/lib/config/hooked_command_mgr.cc131
-rw-r--r--src/lib/config/hooked_command_mgr.h91
-rw-r--r--src/lib/config/tests/Makefile.am6
-rw-r--r--src/lib/config/tests/client_connection_unittests.cc186
-rw-r--r--src/lib/config/tests/command_mgr_unittests.cc272
-rw-r--r--src/lib/config/tests/command_socket_factory_unittests.cc94
-rw-r--r--src/lib/cryptolink/Makefile.am3
-rw-r--r--src/lib/cryptolink/botan_common.h4
-rw-r--r--src/lib/cryptolink/botan_hash.cc46
-rw-r--r--src/lib/cryptolink/botan_hmac.cc55
-rw-r--r--src/lib/cryptolink/botan_link.cc7
-rw-r--r--src/lib/cryptolink/crypto_hash.h2
-rw-r--r--src/lib/cryptolink/crypto_hmac.h2
-rw-r--r--src/lib/cryptolink/openssl_compat.h53
-rw-r--r--src/lib/cryptolink/openssl_hash.cc33
-rw-r--r--src/lib/cryptolink/openssl_hmac.cc58
-rw-r--r--src/lib/dhcp/Makefile.am3
-rw-r--r--src/lib/dhcp/dhcp6.h10
-rw-r--r--src/lib/dhcp/docsis3_option_defs.h4
-rw-r--r--src/lib/dhcp/duid_factory.cc12
-rw-r--r--src/lib/dhcp/iface_mgr.cc10
-rw-r--r--src/lib/dhcp/iface_mgr.h28
-rw-r--r--src/lib/dhcp/iface_mgr_error_handler.h4
-rw-r--r--src/lib/dhcp/iface_mgr_linux.cc4
-rw-r--r--src/lib/dhcp/libdhcp++.cc2
-rw-r--r--src/lib/dhcp/libdhcp++.dox4
-rw-r--r--src/lib/dhcp/libdhcp++.h6
-rw-r--r--src/lib/dhcp/opaque_data_tuple.h6
-rw-r--r--src/lib/dhcp/option4_addrlst.h8
-rw-r--r--src/lib/dhcp/option4_client_fqdn.cc20
-rw-r--r--src/lib/dhcp/option6_client_fqdn.cc10
-rw-r--r--src/lib/dhcp/option6_iaprefix.cc2
-rw-r--r--src/lib/dhcp/option6_iaprefix.h4
-rw-r--r--src/lib/dhcp/option6_pdexclude.h2
-rw-r--r--src/lib/dhcp/option_custom.cc104
-rw-r--r--src/lib/dhcp/option_custom.h40
-rw-r--r--src/lib/dhcp/option_data_types.cc126
-rw-r--r--src/lib/dhcp/option_data_types.h42
-rw-r--r--src/lib/dhcp/option_definition.cc98
-rw-r--r--src/lib/dhcp/option_definition.h49
-rw-r--r--src/lib/dhcp/option_space_container.h4
-rw-r--r--src/lib/dhcp/option_string.h4
-rw-r--r--src/lib/dhcp/option_vendor_class.cc4
-rw-r--r--src/lib/dhcp/option_vendor_class.h4
-rw-r--r--src/lib/dhcp/pkt4.cc4
-rw-r--r--src/lib/dhcp/pkt4.h4
-rw-r--r--src/lib/dhcp/pkt6.cc46
-rw-r--r--src/lib/dhcp/pkt6.h7
-rw-r--r--src/lib/dhcp/pkt_filter.h2
-rw-r--r--src/lib/dhcp/pkt_filter_bpf.cc18
-rw-r--r--src/lib/dhcp/pkt_filter_bpf.h6
-rw-r--r--src/lib/dhcp/pkt_filter_inet.h10
-rw-r--r--src/lib/dhcp/pkt_filter_inet6.cc8
-rw-r--r--src/lib/dhcp/pkt_filter_inet6.h8
-rw-r--r--src/lib/dhcp/pkt_filter_lpf.cc12
-rw-r--r--src/lib/dhcp/pkt_filter_lpf.h4
-rw-r--r--src/lib/dhcp/protocol_util.h4
-rw-r--r--src/lib/dhcp/std_option_defs.h56
-rw-r--r--src/lib/dhcp/tests/duid_factory_unittest.cc8
-rw-r--r--src/lib/dhcp/tests/iface_mgr_test_config.h4
-rw-r--r--src/lib/dhcp/tests/iface_mgr_unittest.cc58
-rw-r--r--src/lib/dhcp/tests/libdhcp++_unittest.cc170
-rw-r--r--src/lib/dhcp/tests/opaque_data_tuple_unittest.cc6
-rw-r--r--src/lib/dhcp/tests/option6_ia_unittest.cc4
-rw-r--r--src/lib/dhcp/tests/option6_iaprefix_unittest.cc2
-rw-r--r--src/lib/dhcp/tests/option_custom_unittest.cc350
-rw-r--r--src/lib/dhcp/tests/option_data_types_unittest.cc135
-rw-r--r--src/lib/dhcp/tests/option_definition_unittest.cc218
-rw-r--r--src/lib/dhcp/tests/option_int_unittest.cc16
-rw-r--r--src/lib/dhcp/tests/option_space_unittest.cc6
-rw-r--r--src/lib/dhcp/tests/option_string_unittest.cc2
-rw-r--r--src/lib/dhcp/tests/option_unittest.cc4
-rw-r--r--src/lib/dhcp/tests/option_vendor_unittest.cc2
-rw-r--r--src/lib/dhcp/tests/pkt4_unittest.cc12
-rw-r--r--src/lib/dhcp/tests/pkt6_unittest.cc42
-rw-r--r--src/lib/dhcp/tests/pkt_captures4.cc4
-rw-r--r--src/lib/dhcp/tests/pkt_captures6.cc6
-rw-r--r--src/lib/dhcp/tests/pkt_filter_test_stub.h6
-rw-r--r--src/lib/dhcp/tests/pkt_filter_test_utils.h4
-rw-r--r--src/lib/dhcp/tests/pkt_filter_unittest.cc4
-rw-r--r--src/lib/dhcp/tests/protocol_util_unittest.cc4
-rw-r--r--src/lib/dhcp_ddns/Makefile.am2
-rw-r--r--src/lib/dhcp_ddns/libdhcp_ddns.dox6
-rw-r--r--src/lib/dhcp_ddns/ncr_io.cc8
-rw-r--r--src/lib/dhcp_ddns/ncr_io.h6
-rw-r--r--src/lib/dhcp_ddns/ncr_msg.h8
-rw-r--r--src/lib/dhcp_ddns/ncr_udp.cc10
-rw-r--r--src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc10
-rw-r--r--src/lib/dhcpsrv/Makefile.am16
-rw-r--r--src/lib/dhcpsrv/addr_utilities.cc77
-rw-r--r--src/lib/dhcpsrv/addr_utilities.h14
-rw-r--r--src/lib/dhcpsrv/alloc_engine.cc70
-rw-r--r--src/lib/dhcpsrv/alloc_engine.h40
-rw-r--r--src/lib/dhcpsrv/alloc_engine_log.h10
-rw-r--r--src/lib/dhcpsrv/base_host_data_source.h44
-rw-r--r--src/lib/dhcpsrv/cfg_4o6.cc49
-rw-r--r--src/lib/dhcpsrv/cfg_4o6.h12
-rw-r--r--src/lib/dhcpsrv/cfg_db_access.cc66
-rw-r--r--src/lib/dhcpsrv/cfg_db_access.h43
-rw-r--r--src/lib/dhcpsrv/cfg_duid.cc38
-rw-r--r--src/lib/dhcpsrv/cfg_duid.h10
-rw-r--r--src/lib/dhcpsrv/cfg_expiration.cc36
-rw-r--r--src/lib/dhcpsrv/cfg_expiration.h16
-rw-r--r--src/lib/dhcpsrv/cfg_host_operations.cc17
-rw-r--r--src/lib/dhcpsrv/cfg_host_operations.h12
-rw-r--r--src/lib/dhcpsrv/cfg_hosts.cc105
-rw-r--r--src/lib/dhcpsrv/cfg_hosts.h104
-rw-r--r--src/lib/dhcpsrv/cfg_hosts_util.cc94
-rw-r--r--src/lib/dhcpsrv/cfg_hosts_util.h53
-rw-r--r--src/lib/dhcpsrv/cfg_iface.cc37
-rw-r--r--src/lib/dhcpsrv/cfg_iface.h30
-rw-r--r--src/lib/dhcpsrv/cfg_mac_source.cc79
-rw-r--r--src/lib/dhcpsrv/cfg_mac_source.h18
-rw-r--r--src/lib/dhcpsrv/cfg_option.cc95
-rw-r--r--src/lib/dhcpsrv/cfg_option.h17
-rw-r--r--src/lib/dhcpsrv/cfg_option_def.cc61
-rw-r--r--src/lib/dhcpsrv/cfg_option_def.h12
-rw-r--r--src/lib/dhcpsrv/cfg_rsoo.cc16
-rw-r--r--src/lib/dhcpsrv/cfg_rsoo.h10
-rw-r--r--src/lib/dhcpsrv/cfg_subnets4.cc83
-rw-r--r--src/lib/dhcpsrv/cfg_subnets4.h77
-rw-r--r--src/lib/dhcpsrv/cfg_subnets6.cc75
-rw-r--r--src/lib/dhcpsrv/cfg_subnets6.h79
-rw-r--r--src/lib/dhcpsrv/cfgmgr.cc62
-rw-r--r--src/lib/dhcpsrv/cfgmgr.h136
-rw-r--r--src/lib/dhcpsrv/client_class_def.cc54
-rw-r--r--src/lib/dhcpsrv/client_class_def.h32
-rw-r--r--src/lib/dhcpsrv/cql_connection.cc2
-rw-r--r--src/lib/dhcpsrv/cql_lease_mgr.cc12
-rw-r--r--src/lib/dhcpsrv/cql_lease_mgr.h28
-rw-r--r--src/lib/dhcpsrv/d2_client_cfg.cc39
-rw-r--r--src/lib/dhcpsrv/d2_client_cfg.h19
-rw-r--r--src/lib/dhcpsrv/d2_client_mgr.h10
-rw-r--r--src/lib/dhcpsrv/daemon.cc29
-rw-r--r--src/lib/dhcpsrv/daemon.h27
-rw-r--r--src/lib/dhcpsrv/database_backends.dox4
-rw-r--r--src/lib/dhcpsrv/dhcpsrv_log.h12
-rw-r--r--src/lib/dhcpsrv/dhcpsrv_messages.mes96
-rw-r--r--src/lib/dhcpsrv/host.cc133
-rw-r--r--src/lib/dhcpsrv/host.h24
-rw-r--r--src/lib/dhcpsrv/host_container.h4
-rw-r--r--src/lib/dhcpsrv/host_data_source_factory.cc2
-rw-r--r--src/lib/dhcpsrv/host_mgr.cc89
-rw-r--r--src/lib/dhcpsrv/host_mgr.h65
-rw-r--r--src/lib/dhcpsrv/hosts_log.h10
-rw-r--r--src/lib/dhcpsrv/hosts_messages.mes32
-rw-r--r--src/lib/dhcpsrv/lease.cc58
-rw-r--r--src/lib/dhcpsrv/lease.h19
-rw-r--r--src/lib/dhcpsrv/lease_file_loader.h6
-rw-r--r--src/lib/dhcpsrv/lease_file_stats.h8
-rw-r--r--src/lib/dhcpsrv/lease_mgr.cc21
-rw-r--r--src/lib/dhcpsrv/lease_mgr.h30
-rw-r--r--src/lib/dhcpsrv/libdhcpsrv.dox84
-rw-r--r--src/lib/dhcpsrv/logging_info.cc67
-rw-r--r--src/lib/dhcpsrv/logging_info.h19
-rw-r--r--src/lib/dhcpsrv/memfile_lease_mgr.cc113
-rw-r--r--src/lib/dhcpsrv/memfile_lease_mgr.h22
-rw-r--r--src/lib/dhcpsrv/memfile_lease_storage.h28
-rw-r--r--src/lib/dhcpsrv/mysql_connection.cc36
-rw-r--r--src/lib/dhcpsrv/mysql_connection.h6
-rw-r--r--src/lib/dhcpsrv/mysql_host_data_source.cc173
-rw-r--r--src/lib/dhcpsrv/mysql_host_data_source.h46
-rw-r--r--src/lib/dhcpsrv/mysql_lease_mgr.cc26
-rw-r--r--src/lib/dhcpsrv/mysql_lease_mgr.h34
-rw-r--r--src/lib/dhcpsrv/parsers/client_class_def_parser.cc186
-rw-r--r--src/lib/dhcpsrv/parsers/client_class_def_parser.h120
-rw-r--r--src/lib/dhcpsrv/parsers/dbaccess_parser.cc79
-rw-r--r--src/lib/dhcpsrv/parsers/dbaccess_parser.h63
-rw-r--r--src/lib/dhcpsrv/parsers/dhcp_config_parser.h17
-rw-r--r--src/lib/dhcpsrv/parsers/dhcp_parsers.cc1848
-rw-r--r--src/lib/dhcpsrv/parsers/dhcp_parsers.h1107
-rw-r--r--src/lib/dhcpsrv/parsers/duid_config_parser.cc150
-rw-r--r--src/lib/dhcpsrv/parsers/duid_config_parser.h62
-rw-r--r--src/lib/dhcpsrv/parsers/expiration_config_parser.cc77
-rw-r--r--src/lib/dhcpsrv/parsers/expiration_config_parser.h20
-rw-r--r--src/lib/dhcpsrv/parsers/host_reservation_parser.cc112
-rw-r--r--src/lib/dhcpsrv/parsers/host_reservation_parser.h101
-rw-r--r--src/lib/dhcpsrv/parsers/host_reservations_list_parser.h35
-rw-r--r--src/lib/dhcpsrv/parsers/ifaces_config_parser.cc122
-rw-r--r--src/lib/dhcpsrv/parsers/ifaces_config_parser.h142
-rw-r--r--src/lib/dhcpsrv/parsers/option_data_parser.cc369
-rw-r--r--src/lib/dhcpsrv/parsers/option_data_parser.h182
-rw-r--r--src/lib/dhcpsrv/parsers/simple_parser4.cc155
-rw-r--r--src/lib/dhcpsrv/parsers/simple_parser4.h50
-rw-r--r--src/lib/dhcpsrv/parsers/simple_parser6.cc153
-rw-r--r--src/lib/dhcpsrv/parsers/simple_parser6.h52
-rw-r--r--src/lib/dhcpsrv/pgsql_connection.cc50
-rw-r--r--src/lib/dhcpsrv/pgsql_connection.h15
-rw-r--r--src/lib/dhcpsrv/pgsql_exchange.cc14
-rw-r--r--src/lib/dhcpsrv/pgsql_host_data_source.cc181
-rw-r--r--src/lib/dhcpsrv/pgsql_host_data_source.h50
-rw-r--r--src/lib/dhcpsrv/pgsql_lease_mgr.cc33
-rw-r--r--src/lib/dhcpsrv/pgsql_lease_mgr.h36
-rw-r--r--src/lib/dhcpsrv/pool.cc111
-rw-r--r--src/lib/dhcpsrv/pool.h19
-rw-r--r--src/lib/dhcpsrv/srv_config.cc156
-rw-r--r--src/lib/dhcpsrv/srv_config.h78
-rw-r--r--src/lib/dhcpsrv/subnet.cc220
-rw-r--r--src/lib/dhcpsrv/subnet.h158
-rw-r--r--src/lib/dhcpsrv/subnet_id.h2
-rw-r--r--src/lib/dhcpsrv/tests/Makefile.am1
-rw-r--r--src/lib/dhcpsrv/tests/addr_utilities_unittest.cc91
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc130
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc332
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc46
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc125
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine_utils.cc25
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine_utils.h31
-rw-r--r--src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc6
-rw-r--r--src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc23
-rw-r--r--src/lib/dhcpsrv/tests/cfg_duid_unittest.cc28
-rw-r--r--src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc55
-rw-r--r--src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc15
-rw-r--r--src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc202
-rw-r--r--src/lib/dhcpsrv/tests/cfg_iface_unittest.cc40
-rw-r--r--src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc39
-rw-r--r--src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc58
-rw-r--r--src/lib/dhcpsrv/tests/cfg_option_unittest.cc70
-rw-r--r--src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc13
-rw-r--r--src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc215
-rw-r--r--src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc267
-rw-r--r--src/lib/dhcpsrv/tests/cfgmgr_unittest.cc98
-rw-r--r--src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc221
-rw-r--r--src/lib/dhcpsrv/tests/client_class_def_unittest.cc88
-rw-r--r--src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc10
-rw-r--r--src/lib/dhcpsrv/tests/d2_client_unittest.cc39
-rw-r--r--src/lib/dhcpsrv/tests/d2_udp_unittest.cc14
-rw-r--r--src/lib/dhcpsrv/tests/daemon_unittest.cc4
-rw-r--r--src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc157
-rw-r--r--src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc12
-rw-r--r--src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc1254
-rw-r--r--src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc64
-rw-r--r--src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc16
-rw-r--r--src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc212
-rw-r--r--src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h67
-rw-r--r--src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc85
-rw-r--r--src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h19
-rw-r--r--src/lib/dhcpsrv/tests/host_mgr_unittest.cc22
-rw-r--r--src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc425
-rw-r--r--src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc162
-rw-r--r--src/lib/dhcpsrv/tests/host_unittest.cc11
-rw-r--r--src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc62
-rw-r--r--src/lib/dhcpsrv/tests/lease_file_io.h2
-rw-r--r--src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc4
-rw-r--r--src/lib/dhcpsrv/tests/lease_mgr_unittest.cc14
-rw-r--r--src/lib/dhcpsrv/tests/lease_unittest.cc173
-rw-r--r--src/lib/dhcpsrv/tests/logging_info_unittest.cc25
-rw-r--r--src/lib/dhcpsrv/tests/logging_unittest.cc115
-rw-r--r--src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc67
-rw-r--r--src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc78
-rw-r--r--src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc14
-rw-r--r--src/lib/dhcpsrv/tests/ncr_generator_unittest.cc4
-rw-r--r--src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc14
-rw-r--r--src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc77
-rw-r--r--src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc18
-rw-r--r--src/lib/dhcpsrv/tests/pool_unittest.cc58
-rw-r--r--src/lib/dhcpsrv/tests/srv_config_unittest.cc123
-rw-r--r--src/lib/dhcpsrv/tests/subnet_unittest.cc80
-rw-r--r--src/lib/dhcpsrv/tests/test_get_callout_handle.h2
-rw-r--r--src/lib/dhcpsrv/tests/timer_mgr_unittest.cc149
-rw-r--r--src/lib/dhcpsrv/testutils/config_result_check.cc4
-rw-r--r--src/lib/dhcpsrv/testutils/config_result_check.h4
-rw-r--r--src/lib/dhcpsrv/timer_mgr.cc385
-rw-r--r--src/lib/dhcpsrv/timer_mgr.h142
-rw-r--r--src/lib/dhcpsrv/writable_host_data_source.h12
-rw-r--r--src/lib/dns/Makefile.am2
-rw-r--r--src/lib/dns/labelsequence.cc4
-rw-r--r--src/lib/dns/master_loader.cc2
-rw-r--r--src/lib/dns/master_loader.h2
-rw-r--r--src/lib/dns/message.h2
-rw-r--r--src/lib/dns/messagerenderer.h10
-rw-r--r--src/lib/dns/rdatafields.cc4
-rw-r--r--src/lib/dns/rrclass-placeholder.h2
-rw-r--r--src/lib/dns/rrclass.h2
-rw-r--r--src/lib/dns/rrcollator.h6
-rw-r--r--src/lib/dns/rrset_collection_base.h4
-rw-r--r--src/lib/dns/serial.h4
-rw-r--r--src/lib/dns/tests/dns_exceptions_unittest.cc4
-rw-r--r--src/lib/dns/tests/master_loader_unittest.cc116
-rw-r--r--src/lib/dns/tests/masterload_unittest.cc2
-rw-r--r--src/lib/dns/tests/messagerenderer_unittest.cc2
-rw-r--r--src/lib/dns/tests/name_unittest.cc4
-rw-r--r--src/lib/dns/tests/rdata_nsec3_unittest.cc4
-rw-r--r--src/lib/dns/tests/rdata_nsec3param_like_unittest.cc4
-rw-r--r--src/lib/dns/tests/rdata_nsec_unittest.cc4
-rw-r--r--src/lib/dns/tests/rdata_pimpl_holder_unittest.cc4
-rw-r--r--src/lib/dns/tests/rdata_soa_unittest.cc4
-rw-r--r--src/lib/dns/tests/rdata_srv_unittest.cc4
-rw-r--r--src/lib/dns/tests/rdata_tsig_unittest.cc8
-rw-r--r--src/lib/dns/tests/rrclass_unittest.cc4
-rw-r--r--src/lib/dns/tests/testdata/message_toWire5.spec2
-rw-r--r--src/lib/dns/tests/testdata/rdata_cname_fromWire2
-rw-r--r--src/lib/dns/tests/testdata/rdata_dhcid_fromWire2
-rw-r--r--src/lib/dns/tests/testdata/rdata_dname_fromWire2
-rw-r--r--src/lib/dns/tests/testdata/rdata_mx_fromWire2
-rw-r--r--src/lib/dns/tests/testdata/rdata_ns_fromWire2
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec2
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec2
-rw-r--r--src/lib/dns/tests/testdata/rdata_soa_fromWire2
-rw-r--r--src/lib/dns/tests/testdata/rdata_srv_fromWire2
-rw-r--r--src/lib/dns/tests/testdata/rdata_txt_fromWire12
-rw-r--r--src/lib/dns/tests/unittest_util.h2
-rw-r--r--src/lib/dns/tests/zone_checker_unittest.cc6
-rw-r--r--src/lib/dns/tsig.h4
-rw-r--r--src/lib/eval/Makefile.am2
-rw-r--r--src/lib/eval/eval.dox4
-rw-r--r--src/lib/eval/eval_context.cc35
-rw-r--r--src/lib/eval/eval_context.h38
-rw-r--r--src/lib/eval/eval_log.h6
-rw-r--r--src/lib/eval/eval_messages.mes2
-rw-r--r--src/lib/eval/evaluate.cc20
-rw-r--r--src/lib/eval/evaluate.h8
-rw-r--r--src/lib/eval/lexer.cc1028
-rw-r--r--src/lib/eval/lexer.ll90
-rw-r--r--src/lib/eval/location.hh2
-rw-r--r--src/lib/eval/parser.cc627
-rw-r--r--src/lib/eval/parser.h213
-rw-r--r--src/lib/eval/parser.yy19
-rw-r--r--src/lib/eval/position.hh2
-rw-r--r--src/lib/eval/stack.hh2
-rw-r--r--src/lib/eval/tests/boolean_unittest.cc6
-rw-r--r--src/lib/eval/tests/context_unittest.cc48
-rw-r--r--src/lib/eval/tests/evaluate_unittest.cc108
-rw-r--r--src/lib/eval/tests/token_unittest.cc75
-rw-r--r--src/lib/eval/token.cc35
-rw-r--r--src/lib/eval/token.h28
-rw-r--r--src/lib/exceptions/exceptions.h8
-rw-r--r--src/lib/exceptions/tests/exceptions_unittest.cc13
-rw-r--r--src/lib/hooks/Makefile.am4
-rw-r--r--src/lib/hooks/callout_manager.cc55
-rw-r--r--src/lib/hooks/callout_manager.h73
-rw-r--r--src/lib/hooks/hooks_config.cc127
-rw-r--r--src/lib/hooks/hooks_config.h108
-rw-r--r--src/lib/hooks/hooks_log.cc8
-rw-r--r--src/lib/hooks/hooks_log.h8
-rw-r--r--src/lib/hooks/hooks_manager.cc37
-rw-r--r--src/lib/hooks/hooks_manager.h63
-rw-r--r--src/lib/hooks/hooks_messages.mes2
-rw-r--r--src/lib/hooks/hooks_parser.cc110
-rw-r--r--src/lib/hooks/hooks_parser.h65
-rw-r--r--src/lib/hooks/hooks_user.dox6
-rw-r--r--src/lib/hooks/library_handle.cc14
-rw-r--r--src/lib/hooks/library_handle.h33
-rw-r--r--src/lib/hooks/library_manager.cc5
-rw-r--r--src/lib/hooks/library_manager.h6
-rw-r--r--src/lib/hooks/library_manager_collection.cc40
-rw-r--r--src/lib/hooks/library_manager_collection.h6
-rw-r--r--src/lib/hooks/server_hooks.cc57
-rw-r--r--src/lib/hooks/server_hooks.h53
-rw-r--r--src/lib/hooks/tests/.gitignore1
-rw-r--r--src/lib/hooks/tests/callout_manager_unittest.cc50
-rw-r--r--src/lib/hooks/tests/common_test_class.h42
-rw-r--r--src/lib/hooks/tests/full_callout_library.cc36
-rw-r--r--src/lib/hooks/tests/hooks_manager_unittest.cc198
-rw-r--r--src/lib/hooks/tests/library_manager_unittest.cc42
-rw-r--r--src/lib/hooks/tests/load_callout_library.cc40
-rw-r--r--src/lib/hooks/tests/server_hooks_unittest.cc52
-rw-r--r--src/lib/http/.gitignore3
-rw-r--r--src/lib/http/Makefile.am62
-rw-r--r--src/lib/http/connection.cc273
-rw-r--r--src/lib/http/connection.h219
-rw-r--r--src/lib/http/connection_pool.cc36
-rw-r--r--src/lib/http/connection_pool.h60
-rw-r--r--src/lib/http/date_time.cc156
-rw-r--r--src/lib/http/date_time.h160
-rw-r--r--src/lib/http/header_context.h23
-rw-r--r--src/lib/http/http_acceptor.h27
-rw-r--r--src/lib/http/http_log.cc19
-rw-r--r--src/lib/http/http_log.h23
-rw-r--r--src/lib/http/http_messages.mes45
-rw-r--r--src/lib/http/http_types.h52
-rw-r--r--src/lib/http/listener.cc219
-rw-r--r--src/lib/http/listener.h120
-rw-r--r--src/lib/http/post_request.cc20
-rw-r--r--src/lib/http/post_request.h40
-rw-r--r--src/lib/http/post_request_json.cc75
-rw-r--r--src/lib/http/post_request_json.h83
-rw-r--r--src/lib/http/request.cc259
-rw-r--r--src/lib/http/request.h296
-rw-r--r--src/lib/http/request_context.h44
-rw-r--r--src/lib/http/request_parser.cc676
-rw-r--r--src/lib/http/request_parser.h463
-rw-r--r--src/lib/http/response.cc145
-rw-r--r--src/lib/http/response.h242
-rw-r--r--src/lib/http/response_creator.cc31
-rw-r--r--src/lib/http/response_creator.h113
-rw-r--r--src/lib/http/response_creator_factory.h59
-rw-r--r--src/lib/http/response_json.cc52
-rw-r--r--src/lib/http/response_json.h66
-rw-r--r--src/lib/http/tests/.gitignore1
-rw-r--r--src/lib/http/tests/Makefile.am51
-rw-r--r--src/lib/http/tests/connection_pool_unittests.cc192
-rw-r--r--src/lib/http/tests/date_time_unittests.cc190
-rw-r--r--src/lib/http/tests/listener_unittests.cc458
-rw-r--r--src/lib/http/tests/post_request_json_unittests.cc173
-rw-r--r--src/lib/http/tests/post_request_unittests.cc72
-rw-r--r--src/lib/http/tests/request_parser_unittests.cc328
-rw-r--r--src/lib/http/tests/request_test.h84
-rw-r--r--src/lib/http/tests/request_unittests.cc158
-rw-r--r--src/lib/http/tests/response_creator_unittests.cc124
-rw-r--r--src/lib/http/tests/response_json_unittests.cc149
-rw-r--r--src/lib/http/tests/response_test.h38
-rw-r--r--src/lib/http/tests/response_unittests.cc167
-rw-r--r--src/lib/http/tests/run_unittests.cc19
-rw-r--r--src/lib/log/Makefile.am4
-rw-r--r--src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc10
-rw-r--r--src/lib/log/interprocess/tests/run_unittests.cc4
-rw-r--r--src/lib/log/log_dbglevels.cc22
-rw-r--r--src/lib/log/log_dbglevels.h25
-rw-r--r--src/lib/log/log_messages.cc4
-rw-r--r--src/lib/log/logger_impl.cc4
-rw-r--r--src/lib/log/logger_manager_impl.cc7
-rw-r--r--src/lib/log/message_initializer.h6
-rw-r--r--src/lib/log/tests/logger_example.cc4
-rw-r--r--src/lib/log/tests/logger_manager_unittest.cc4
-rw-r--r--src/lib/process/Makefile.am4
-rw-r--r--src/lib/process/d_cfg_mgr.cc131
-rw-r--r--src/lib/process/d_cfg_mgr.h162
-rw-r--r--src/lib/process/d_controller.cc332
-rw-r--r--src/lib/process/d_controller.h261
-rw-r--r--src/lib/process/d_process.h45
-rw-r--r--src/lib/process/io_service_signal.cc2
-rw-r--r--src/lib/process/io_service_signal.h10
-rw-r--r--src/lib/process/libprocess.dox2
-rw-r--r--src/lib/process/process_messages.mes27
-rw-r--r--src/lib/process/tests/d_cfg_mgr_unittests.cc165
-rw-r--r--src/lib/process/tests/d_controller_unittests.cc121
-rw-r--r--src/lib/process/tests/io_service_signal_unittests.cc12
-rw-r--r--src/lib/process/testutils/d_test_stubs.cc167
-rw-r--r--src/lib/process/testutils/d_test_stubs.h218
-rw-r--r--src/lib/stats/stats_mgr.cc8
-rw-r--r--src/lib/stats/stats_mgr.h6
-rw-r--r--src/lib/stats/tests/observation_unittest.cc6
-rw-r--r--src/lib/stats/tests/stats_mgr_unittest.cc4
-rw-r--r--src/lib/testutils/Makefile.am1
-rw-r--r--src/lib/testutils/dhcp_test_lib.sh.in29
-rw-r--r--src/lib/testutils/io_utils.cc86
-rw-r--r--src/lib/testutils/io_utils.h21
-rw-r--r--src/lib/testutils/test_to_element.cc34
-rw-r--r--src/lib/testutils/test_to_element.h91
-rw-r--r--src/lib/testutils/unix_control_client.cc15
-rw-r--r--src/lib/testutils/unix_control_client.h7
-rw-r--r--src/lib/util/Makefile.am2
-rw-r--r--src/lib/util/csv_file.h4
-rw-r--r--src/lib/util/python/const2hdr.py2
-rw-r--r--src/lib/util/python/gen_wiredata.py.in2
-rw-r--r--src/lib/util/range_utilities.h4
-rw-r--r--src/lib/util/state_model.cc6
-rw-r--r--src/lib/util/state_model.h8
-rw-r--r--src/lib/util/stopwatch_impl.h4
-rw-r--r--src/lib/util/strutil.cc79
-rw-r--r--src/lib/util/strutil.h8
-rw-r--r--src/lib/util/tests/csv_file_unittest.cc4
-rw-r--r--src/lib/util/tests/process_spawn_unittest.cc21
-rw-r--r--src/lib/util/tests/staged_value_unittest.cc4
-rw-r--r--src/lib/util/tests/state_model_unittest.cc10
-rw-r--r--src/lib/util/tests/stopwatch_unittest.cc4
-rw-r--r--src/lib/util/tests/strutil_unittest.cc40
-rw-r--r--src/lib/util/tests/versioned_csv_file_unittest.cc12
-rw-r--r--src/lib/util/tests/watch_socket_unittests.cc2
-rw-r--r--src/lib/util/threads/tests/run_unittests.cc4
-rw-r--r--src/lib/util/versioned_csv_file.h6
-rw-r--r--src/lib/util/watch_socket.cc7
522 files changed, 29957 insertions, 8834 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index dbbdb8f11b..9de3269c32 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,3 +1,3 @@
# The following build order must be maintained.
-SUBDIRS = exceptions util log cryptolink dns cc hooks asiolink testutils dhcp config \
- stats asiodns dhcp_ddns eval dhcpsrv cfgrpt process
+SUBDIRS = exceptions util log cryptolink dns asiolink cc testutils hooks dhcp \
+ config stats asiodns dhcp_ddns eval dhcpsrv cfgrpt process http
diff --git a/src/lib/asiodns/README b/src/lib/asiodns/README
index e47b166638..e785845342 100644
--- a/src/lib/asiodns/README
+++ b/src/lib/asiodns/README
@@ -5,11 +5,11 @@ These DNS server and client routines are written using the "stackless
coroutine" pattern invented by Chris Kohlhoff and described at
http://blog.think-async.com/2010/03/potted-guide-to-stackless-coroutines.html.
This is intended to simplify development a bit, since it allows the
-routines to be written in a straightfowrard step-step-step fashion rather
+routines to be written in a straightforward step-step-step fashion rather
than as a complex chain of separate handler functions.
Coroutine objects (i.e., UDPServer, TCPServer and IOFetch) are objects
-with reenterable operator() members. When an instance of one of these
+with reentrant operator() members. When an instance of one of these
classes is called as a function, it resumes at the position where it left
off. Thus, a UDPServer can issue an asynchronous I/O call and specify
itself as the handler object; when the call completes, the UDPServer
@@ -47,7 +47,7 @@ In an authoritative server, the DNSLookup implementation would examine
the query, look up the answer, then call "resume". (See the diagram
in doc/auth_process.jpg.)
-In a recursive server, the DNSLookup impelemtation would initiate a
+In a recursive server, the DNSLookup implementation would initiate a
DNSQuery, which in turn would be responsible for calling the server's
"resume" method. (See the diagram in doc/recursive_process.jpg.)
@@ -89,7 +89,7 @@ fetch logic:
|
IOAsioSocket
|
- +-----+-----+
+ +-----+-----+
| |
UDPSocket TCPSocket
@@ -122,7 +122,7 @@ protocol to use. The sequence is:
if (! synchronous) {
YIELD;
}
- YIELD asyncSend(query) // Send query
+ YIELD asyncSend(query) // Send query
do {
YIELD asyncReceive(response) // Read response
} while (! complete(response))
diff --git a/src/lib/asiodns/io_fetch.cc b/src/lib/asiodns/io_fetch.cc
index 571c54fae8..a836871aa0 100644
--- a/src/lib/asiodns/io_fetch.cc
+++ b/src/lib/asiodns/io_fetch.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -232,7 +232,11 @@ IOFetch::operator()(boost::system::error_code ec, size_t length) {
if (data_->stopped) {
return;
- } else if (ec) {
+
+ // On Debian it has been often observed that boost::asio async
+ // operations result in EINPROGRESS. This doesn't necessarily
+ // indicate an issue. Thus, we continue as if no error occurred.
+ } else if (ec && (ec.value() != boost::asio::error::in_progress)) {
logIOFailure(ec);
return;
}
@@ -293,7 +297,7 @@ IOFetch::operator()(boost::system::error_code ec, size_t length) {
// So... we need to loop until we have at least two bytes, then store
// the expected amount of data. Then we need to loop until we have
// received all the data before copying it back to the user's buffer.
- // And we want to minimise the amount of copying...
+ // And we want to minimize the amount of copying...
data_->origin = ASIODNS_READ_DATA;
data_->cumulative = 0; // No data yet received
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index ab1ee62fc1..073eaef0a4 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . tests
+SUBDIRS = . testutils tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
@@ -15,22 +15,27 @@ CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libkea-asiolink.la
-libkea_asiolink_la_LDFLAGS = -no-undefined -version-info 3:0:0
+libkea_asiolink_la_LDFLAGS = -no-undefined -version-info 4:0:0
libkea_asiolink_la_SOURCES = asiolink.h
libkea_asiolink_la_SOURCES += asio_wrapper.h
libkea_asiolink_la_SOURCES += dummy_io_cb.h
libkea_asiolink_la_SOURCES += interval_timer.cc interval_timer.h
+libkea_asiolink_la_SOURCES += io_acceptor.h
libkea_asiolink_la_SOURCES += io_address.cc io_address.h
libkea_asiolink_la_SOURCES += io_asio_socket.h
libkea_asiolink_la_SOURCES += io_endpoint.cc io_endpoint.h
libkea_asiolink_la_SOURCES += io_error.h
libkea_asiolink_la_SOURCES += io_service.h io_service.cc
libkea_asiolink_la_SOURCES += io_socket.h io_socket.cc
+libkea_asiolink_la_SOURCES += tcp_acceptor.h
libkea_asiolink_la_SOURCES += tcp_endpoint.h
libkea_asiolink_la_SOURCES += tcp_socket.h
libkea_asiolink_la_SOURCES += udp_endpoint.h
libkea_asiolink_la_SOURCES += udp_socket.h
+libkea_asiolink_la_SOURCES += unix_domain_socket.cc unix_domain_socket.h
+libkea_asiolink_la_SOURCES += unix_domain_socket_acceptor.h
+libkea_asiolink_la_SOURCES += unix_domain_socket_endpoint.h
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# KEA_CXXFLAGS)
diff --git a/src/lib/asiolink/asio_wrapper.h b/src/lib/asiolink/asio_wrapper.h
index 402d9c755a..5369a807b8 100644
--- a/src/lib/asiolink/asio_wrapper.h
+++ b/src/lib/asiolink/asio_wrapper.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,11 +6,19 @@
#ifndef ASIO_WRAPPER_H
#define ASIO_WRAPPER_H 1
-// !!! IMPORTANT !!!!
+// !!! IMPORTANT THIS IS A HACK FOR BOOST HEADERS ONLY BUILDING !!!!
+//
+// As of #5215 (Kea 1.3) The default build configuration is to link with
+// Boost's system library (boost_system) rather than build with Boost's
+// headers only. Linking with the boost_system eliminates the issue as
+// detailed below. This file exists solely for the purpose of allowing
+// people to attempt to build headers only. ISC DOES NOT RECOMMEND
+// building Kea with Boost headers only.
+//
// This file must be included anywhere one would normally have included
// boost/asio.hpp. Until the issue described below is resolved in some
-// other fashion asio.hpp should not be included other than through
-// this file.
+// other fashion, (or we abandon support for headers only building)
+// asio.hpp MUST NOT be included other than through this file.
//
// The optimizer as of gcc 5.2.0, may not reliably ensure a single value
// returned by boost::system::system_category() within a translation unit
@@ -30,12 +38,12 @@
//
// which involve implicit conversion of enumerates to error_code instances
// to not evaluate correctly. During the implicit conversion the error_code
-// instances may be assigned differeing values error_code:m_cat. This
+// instances may be assigned differing values error_code:m_cat. This
// causes two instances of error_code which should have been equal to
// to not be equal.
//
-// The problem disappers if either error handling code is not built header
-// only as this results in a single definiton of system_category() supplied
+// The problem disappears if either error handling code is not built header
+// only as this results in a single definition of system_category() supplied
// by libboost_system; or the error handling code is not optimized.
//
// We're doing the test here, rather than in configure to guard against the
diff --git a/src/lib/asiolink/interval_timer.cc b/src/lib/asiolink/interval_timer.cc
index c173e8dca0..2900b71cab 100644
--- a/src/lib/asiolink/interval_timer.cc
+++ b/src/lib/asiolink/interval_timer.cc
@@ -76,8 +76,8 @@ IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
const long interval,
const IntervalTimer::Mode& mode)
{
- // Interval should not be less than or equal to 0.
- if (interval <= 0) {
+ // Interval should not be less than 0.
+ if (interval < 0) {
isc_throw(isc::BadValue, "Interval should not be less than or "
"equal to 0");
}
diff --git a/src/lib/asiolink/io_acceptor.h b/src/lib/asiolink/io_acceptor.h
new file mode 100644
index 0000000000..c493d3427e
--- /dev/null
+++ b/src/lib/asiolink/io_acceptor.h
@@ -0,0 +1,132 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef IO_ACCEPTOR_H
+#define IO_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Base class for acceptor services in Kea.
+///
+/// This is a wrapper class for ASIO acceptor service. Classes implementing
+/// services for specific protocol types should derive from this class.
+///
+/// Acceptor is an IO object which accepts incoming connections into a socket
+/// object. This socket is then used for data transmission from the client
+/// to server and back. The acceptor is continued to be used to accept new
+/// connections while the accepted connection is active.
+///
+/// @tparam ProtocolType ASIO protocol type, e.g. stream_protocol
+/// @tparam CallbackType Callback function type which should have the following
+/// signature: @c void(const boost::system::error_code&).
+template<typename ProtocolType, typename CallbackType>
+class IOAcceptor : public IOSocket {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ explicit IOAcceptor(IOService& io_service)
+ : IOSocket(),
+ acceptor_(new typename ProtocolType::acceptor(io_service.get_io_service())) {
+ }
+
+ /// @brief Destructor.
+ virtual ~IOAcceptor() { }
+
+ /// @brief Returns file descriptor of the underlying socket.
+ virtual int getNative() const {
+ return (acceptor_->native());
+ }
+
+ /// @brief Opens acceptor socket given the endpoint.
+ ///
+ /// @param endpoint Reference to the endpoint object defining local
+ /// acceptor endpoint.
+ ///
+ /// @tparam EndpointType Endpoint type.
+ template<typename EndpointType>
+ void open(const EndpointType& endpoint) {
+ acceptor_->open(endpoint.getASIOEndpoint().protocol());
+ }
+
+ /// @brief Binds socket to an endpoint.
+ ///
+ /// @param endpoint Reference to the endpoint object defining local
+ /// acceptor endpoint.
+ ///
+ /// @tparam EndpointType Endpoint type.
+ template<typename EndpointType>
+ void bind(const EndpointType& endpoint) {
+ acceptor_->bind(endpoint.getASIOEndpoint());
+ }
+
+ /// @brief Sets socket option.
+ ///
+ /// @param socket_option Reference to the object encapsulating an option to
+ /// be set for the socket.
+ /// @tparam SettableSocketOption Type of the object encapsulating socket option
+ /// being set.
+ template<typename SettableSocketOption>
+ void setOption(const SettableSocketOption& socket_option) {
+ acceptor_->set_option(socket_option);
+ }
+
+ /// @brief Starts listening new connections.
+ void listen() {
+ acceptor_->listen();
+ }
+
+ /// @brief Checks if the acceptor is open.
+ ///
+ /// @return true if acceptor is open.
+ bool isOpen() const {
+ return (acceptor_->is_open());
+ }
+
+ /// @brief Closes the acceptor.
+ void close() const {
+ acceptor_->close();
+ }
+
+protected:
+
+ /// @brief Asynchronously accept new connection.
+ ///
+ /// This method accepts new connection into the specified socket. When the
+ /// new connection arrives or an error occurs the specified callback
+ /// function is invoked.
+ ///
+ /// @param socket Socket into which connection should be accepted.
+ /// @param callback Callback function to be invoked when the new connection
+ /// arrives.
+ /// @tparam SocketType Socket type, e.g. @ref UnixDomainSocket. It must
+ /// implement @c getASIOSocket method.
+ template<typename SocketType>
+ void asyncAcceptInternal(const SocketType& socket,
+ const CallbackType& callback) {
+ acceptor_->async_accept(socket.getASIOSocket(), callback);
+ }
+
+
+ /// @brief Underlying ASIO acceptor implementation.
+ boost::shared_ptr<typename ProtocolType::acceptor> acceptor_;
+
+};
+
+
+} // end of namespace asiolink
+} // end of isc
+
+#endif // IO_ACCEPTOR_H
diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h
index 1d9326a586..4b0fda0af1 100644
--- a/src/lib/asiolink/io_address.h
+++ b/src/lib/asiolink/io_address.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -67,11 +67,11 @@ public:
/// @brief Constructor for ip::address_v4 object.
///
- /// This constructor is intented to be used when constructing
+ /// This constructor is intended to be used when constructing
/// IPv4 address out of uint32_t type. Passed value must be in
/// network byte order
///
- /// @param v4address IPv4 address represnted by uint32_t
+ /// @param v4address IPv4 address represented by uint32_t
IOAddress(uint32_t v4address);
/// \brief Convert the address to a string.
@@ -183,7 +183,7 @@ public:
///
/// It is useful for comparing which address is bigger.
/// Operations within one protocol family are obvious.
- /// Comparisons between v4 and v6 will allways return v4
+ /// Comparisons between v4 and v6 will always return v4
/// being smaller. This follows boost::boost::asio::ip implementation
bool lessThan(const IOAddress& other) const {
if (this->getFamily() == other.getFamily()) {
diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h
index faeb6171e3..cdb4074db2 100644
--- a/src/lib/asiolink/io_asio_socket.h
+++ b/src/lib/asiolink/io_asio_socket.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -78,6 +78,7 @@ class IOEndpoint;
template <typename C>
class IOAsioSocket : public IOSocket {
+
///
/// \name Constructors and Destructor
///
diff --git a/src/lib/asiolink/io_service.cc b/src/lib/asiolink/io_service.cc
index c50ffead6f..4aebddc9b3 100644
--- a/src/lib/asiolink/io_service.cc
+++ b/src/lib/asiolink/io_service.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
#include <unistd.h> // for some IPC/network system calls
#include <netinet/in.h>
+#include <boost/shared_ptr.hpp>
#include <sys/socket.h>
namespace isc {
@@ -40,7 +41,7 @@ public:
/// \brief The constructor
IOServiceImpl() :
io_service_(),
- work_(io_service_)
+ work_(new boost::asio::io_service::work(io_service_))
{};
/// \brief The destructor.
~IOServiceImpl() {};
@@ -76,6 +77,12 @@ public:
/// This will return the control to the caller of the \c run() method.
void stop() { io_service_.stop();} ;
+ /// \brief Removes IO service work object to let it finish running
+ /// when all handlers have been invoked.
+ void stopWork() {
+ work_.reset();
+ }
+
/// \brief Return the native \c io_service object used in this wrapper.
///
/// This is a short term work around to support other Kea modules
@@ -89,7 +96,7 @@ public:
}
private:
boost::asio::io_service io_service_;
- boost::asio::io_service::work work_;
+ boost::shared_ptr<boost::asio::io_service::work> work_;
};
IOService::IOService() {
@@ -120,6 +127,11 @@ IOService::stop() {
io_impl_->stop();
}
+void
+IOService::stopWork() {
+ io_impl_->stopWork();
+}
+
boost::asio::io_service&
IOService::get_io_service() {
return (io_impl_->get_io_service());
diff --git a/src/lib/asiolink/io_service.h b/src/lib/asiolink/io_service.h
index ad952bda17..e9e402d114 100644
--- a/src/lib/asiolink/io_service.h
+++ b/src/lib/asiolink/io_service.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -64,6 +64,10 @@ public:
/// This will return the control to the caller of the \c run() method.
void stop();
+ /// \brief Removes IO service work object to let it finish running
+ /// when all handlers have been invoked.
+ void stopWork();
+
/// \brief Return the native \c io_service object used in this wrapper.
///
/// This is a short term work around to support other Kea modules
diff --git a/src/lib/asiolink/io_socket.h b/src/lib/asiolink/io_socket.h
index efb2eebf7b..9c9cee16fd 100644
--- a/src/lib/asiolink/io_socket.h
+++ b/src/lib/asiolink/io_socket.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -35,6 +35,16 @@ namespace asiolink {
/// derived class for testing purposes rather than providing factory methods
/// (i.e., getDummy variants below).
class IOSocket {
+public:
+
+ /// @name Types of objects encapsulating socket options.
+ //@{
+
+ /// @brief Represents SO_REUSEADDR socket option.
+ typedef boost::asio::socket_base::reuse_address ReuseAddress;
+
+ //@}
+
///
/// \name Constructors and Destructor
///
diff --git a/src/lib/asiolink/tcp_acceptor.h b/src/lib/asiolink/tcp_acceptor.h
new file mode 100644
index 0000000000..4fab1ddbbe
--- /dev/null
+++ b/src/lib/asiolink/tcp_acceptor.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TCP_ACCEPTOR_H
+#define TCP_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_acceptor.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <boost/shared_ptr.hpp>
+#include <netinet/in.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Provides a service for accepting new TCP connections.
+///
+/// Internally it uses @c boost::asio::ip::tcp::acceptor class to implement
+/// the acceptor service.
+///
+/// @tparam C Acceptor callback type.
+template<typename C>
+class TCPAcceptor : public IOAcceptor<boost::asio::ip::tcp, C> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service.
+ explicit TCPAcceptor(IOService& io_service)
+ : IOAcceptor<boost::asio::ip::tcp, C>(io_service) {
+ }
+
+ /// @brief Returns protocol of the socket.
+ ///
+ /// @return IPPROTO_TCP.
+ virtual int getProtocol() const final {
+ return (IPPROTO_TCP);
+ }
+
+ /// @brief Asynchronously accept new connection.
+ ///
+ /// This method accepts new connection into the specified socket. When the
+ /// new connection arrives or an error occurs the specified callback function
+ /// is invoked.
+ ///
+ /// @param socket Socket into which connection should be accepted.
+ /// @param callback Callback function to be invoked when the new connection
+ /// arrives.
+ /// @tparam SocketCallback Type of the callback for the @ref TCPSocket.
+ template<typename SocketCallback>
+ void asyncAccept(const TCPSocket<SocketCallback>& socket, C& callback) {
+ IOAcceptor<boost::asio::ip::tcp, C>::asyncAcceptInternal(socket, callback);
+ }
+};
+
+
+} // namespace asiolink
+} // namespace isc
+
+#endif
diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h
index a3946fe586..adf74d1f0f 100644
--- a/src/lib/asiolink/tcp_socket.h
+++ b/src/lib/asiolink/tcp_socket.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -110,9 +110,24 @@ public:
/// \param endpoint Target of the send. (Unused for a TCP socket because
/// that was determined when the connection was opened.)
/// \param callback Callback object.
+ /// \throw BufferTooLarge on attempt to send a buffer larger than 64kB.
virtual void asyncSend(const void* data, size_t length,
const IOEndpoint* endpoint, C& callback);
+ /// \brief Send Asynchronously without count.
+ ///
+ /// This variant of the method sends data over the TCP socket without
+ /// preceding the data with a data count. Eventually, we should migrate
+ /// the virtual method to not insert the count but there are existing
+ /// classes using the count. Once this migration is done, the existing
+ /// virtual method should be replaced by this method.
+ ///
+ /// \param data Data to send
+ /// \param length Length of data to send
+ /// \param callback Callback object.
+ /// \throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+ void asyncSend(const void* data, size_t length, C& callback);
+
/// \brief Receive Asynchronously
///
/// Calls the underlying socket's async_receive() method to read a packet
@@ -153,6 +168,12 @@ public:
/// \brief Close socket
virtual void close();
+ /// \brief Returns reference to the underlying ASIO socket.
+ ///
+ /// \return Reference to underlying ASIO socket.
+ virtual boost::asio::ip::tcp::socket& getASIOSocket() const {
+ return (socket_);
+ }
private:
// Two variables to hold the socket - a socket and a pointer to it. This
@@ -160,7 +181,6 @@ private:
// construction, or where it is asked to manage its own socket.
boost::asio::ip::tcp::socket* socket_ptr_; ///< Pointer to own socket
boost::asio::ip::tcp::socket& socket_; ///< Socket
- bool isopen_; ///< true when socket is open
// TODO: Remove temporary buffer
// The current implementation copies the buffer passed to asyncSend() into
@@ -182,7 +202,7 @@ private:
template <typename C>
TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) :
- socket_ptr_(NULL), socket_(socket), isopen_(true), send_buffer_()
+ socket_ptr_(NULL), socket_(socket), send_buffer_()
{
}
@@ -191,7 +211,7 @@ TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) :
template <typename C>
TCPSocket<C>::TCPSocket(IOService& service) :
socket_ptr_(new boost::asio::ip::tcp::socket(service.get_io_service())),
- socket_(*socket_ptr_), isopen_(false)
+ socket_(*socket_ptr_)
{
}
@@ -211,14 +231,13 @@ TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
// Ignore opens on already-open socket. Don't throw a failure because
// of uncertainties as to what precedes whan when using asynchronous I/O.
// At also allows us a treat a passed-in socket as a self-managed socket.
- if (!isopen_) {
+ if (!socket_.is_open()) {
if (endpoint->getFamily() == AF_INET) {
socket_.open(boost::asio::ip::tcp::v4());
}
else {
socket_.open(boost::asio::ip::tcp::v6());
}
- isopen_ = true;
// Set options on the socket:
@@ -245,10 +264,34 @@ TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
// an exception if this is the case.
template <typename C> void
+TCPSocket<C>::asyncSend(const void* data, size_t length, C& callback)
+{
+ if (socket_.is_open()) {
+
+ try {
+ send_buffer_.reset(new isc::util::OutputBuffer(length));
+ send_buffer_->writeData(data, length);
+
+ // Send the data.
+ socket_.async_send(boost::asio::buffer(send_buffer_->getData(),
+ send_buffer_->getLength()),
+ callback);
+ } catch (boost::numeric::bad_numeric_cast&) {
+ isc_throw(BufferTooLarge,
+ "attempt to send buffer larger than 64kB");
+ }
+
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to send on a TCP socket that is not open");
+ }
+}
+
+template <typename C> void
TCPSocket<C>::asyncSend(const void* data, size_t length,
const IOEndpoint*, C& callback)
{
- if (isopen_) {
+ if (socket_.is_open()) {
// Need to copy the data into a temporary buffer and precede it with
// a two-byte count field.
@@ -283,7 +326,7 @@ template <typename C> void
TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
IOEndpoint* endpoint, C& callback)
{
- if (isopen_) {
+ if (socket_.is_open()) {
// Upconvert to a TCPEndpoint. We need to do this because although
// IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
// does not contain a method for getting at the underlying endpoint
@@ -385,7 +428,7 @@ TCPSocket<C>::processReceivedData(const void* staging, size_t length,
template <typename C> void
TCPSocket<C>::cancel() {
- if (isopen_) {
+ if (socket_.is_open()) {
socket_.cancel();
}
}
@@ -395,9 +438,8 @@ TCPSocket<C>::cancel() {
template <typename C> void
TCPSocket<C>::close() {
- if (isopen_ && socket_ptr_) {
+ if (socket_.is_open() && socket_ptr_) {
socket_.close();
- isopen_ = false;
}
}
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index 6e5592a566..4d6ce6b4e5 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -1,6 +1,7 @@
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/asiolink/tests\"
AM_CXXFLAGS = $(KEA_CXXFLAGS)
@@ -8,7 +9,7 @@ if USE_STATIC_LINK
AM_LDFLAGS = -static
endif
-CLEANFILES = *.gcno *.gcda
+CLEANFILES = *.gcno *.gcda test-socket
TESTS_ENVIRONMENT = \
$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
@@ -27,10 +28,13 @@ run_unittests_SOURCES += udp_endpoint_unittest.cc
run_unittests_SOURCES += udp_socket_unittest.cc
run_unittests_SOURCES += io_service_unittest.cc
run_unittests_SOURCES += dummy_io_callback_unittest.cc
+run_unittests_SOURCES += tcp_acceptor_unittest.cc
+run_unittests_SOURCES += unix_domain_socket_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD = $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
diff --git a/src/lib/asiolink/tests/interval_timer_unittest.cc b/src/lib/asiolink/tests/interval_timer_unittest.cc
index 54da9f8935..957486e253 100644
--- a/src/lib/asiolink/tests/interval_timer_unittest.cc
+++ b/src/lib/asiolink/tests/interval_timer_unittest.cc
@@ -154,8 +154,7 @@ TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
// expect throw if call back function is empty
EXPECT_THROW(itimer.setup(IntervalTimer::Callback(), 1),
isc::InvalidParameter);
- // expect throw if interval is not greater than 0
- EXPECT_THROW(itimer.setup(TimerCallBack(this), 0), isc::BadValue);
+ // expect throw if interval is negative.
EXPECT_THROW(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
}
diff --git a/src/lib/asiolink/tests/tcp_acceptor_unittest.cc b/src/lib/asiolink/tests/tcp_acceptor_unittest.cc
new file mode 100644
index 0000000000..a88a07a971
--- /dev/null
+++ b/src/lib/asiolink/tests/tcp_acceptor_unittest.cc
@@ -0,0 +1,442 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_acceptor.h>
+#include <asiolink/tcp_endpoint.h>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <list>
+#include <netinet/in.h>
+#include <string>
+
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Local server address used for testing.
+const char SERVER_ADDRESS[] = "127.0.0.1";
+
+/// @brief Local server port used for testing.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Simple class representing TCP socket callback.
+class SocketCallback {
+public:
+
+ /// @brief Implements callback for the asynchronous operation on the socket.
+ ///
+ /// This callback merely checks if error has occurred and reports this
+ /// error. It does nothing in case of success.
+ ///
+ /// @param ec Error code.
+ /// @param length Length of received data.
+ void operator()(boost::system::error_code ec, size_t length = 0) {
+ if (ec) {
+ ADD_FAILURE() << "error occurred for a socket: " << ec.message();
+ }
+ }
+
+};
+
+/// @brief Entity which can connect to the TCP server endpoint and close the
+/// connection.
+class TCPClient : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new socket instance. It doesn't connect. Call
+ /// connect() to connect to the server.
+ ///
+ /// @param io_service IO service to be stopped on error.
+ explicit TCPClient(IOService& io_service)
+ : io_service_(io_service.get_io_service()), socket_(io_service_) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the underlying socket if it is open.
+ ~TCPClient() {
+ close();
+ }
+
+ /// @brief Connect to the test server address and port.
+ ///
+ /// This method asynchronously connects to the server endpoint and uses the
+ /// connectHandler as a callback function.
+ void connect() {
+ boost::asio::ip::tcp::endpoint
+ endpoint(boost::asio::ip::address::from_string(SERVER_ADDRESS),
+ SERVER_PORT);
+ socket_.async_connect(endpoint,
+ boost::bind(&TCPClient::connectHandler, this,_1));
+ }
+
+ /// @brief Callback function for connect().
+ ///
+ /// This function stops the IO service upon error.
+ ///
+ /// @param ec Error code.
+ void connectHandler(const boost::system::error_code& ec) {
+ if (ec) {
+ // One would expect that async_connect wouldn't return EINPROGRESS
+ // error code, but simply wait for the connection to get
+ // established before the handler is invoked. It turns out, however,
+ // that on some OSes the connect handler may receive this error code
+ // which doesn't necessarily indicate a problem. Making an attempt
+ // to write and read from this socket will typically succeed. So,
+ // we ignore this error.
+ if (ec.value() != boost::asio::error::in_progress) {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ }
+ }
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+private:
+
+ /// @brief Holds reference to the IO service.
+ boost::asio::io_service& io_service_;
+
+ /// @brief A socket used for the connection.
+ boost::asio::ip::tcp::socket socket_;
+
+};
+
+/// @brief Pointer to the TCPClient.
+typedef boost::shared_ptr<TCPClient> TCPClientPtr;
+
+/// @brief A signature of the function implementing callback for the
+/// TCPAcceptor.
+typedef boost::function<void(const boost::system::error_code&)> TCPAcceptorCallback;
+
+/// @brief TCPAcceptor using TCPAcceptorCallback.
+typedef TCPAcceptor<TCPAcceptorCallback> TestTCPAcceptor;
+
+/// @brief Implements asynchronous TCP acceptor service.
+///
+/// It creates a new socket into which connection is accepted. The socket
+/// is retained until class instance exists.
+class Acceptor {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service.
+ /// @param acceptor Reference to the TCP acceptor on which asyncAccept
+ /// will be called.
+ /// @param callback Callback function for the asyncAccept.
+ explicit Acceptor(IOService& io_service, TestTCPAcceptor& acceptor,
+ const TCPAcceptorCallback& callback)
+ : socket_(io_service), acceptor_(acceptor), callback_(callback) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes socket.
+ ~Acceptor() {
+ socket_.close();
+ }
+
+ /// @brief Asynchronous accept new connection.
+ void accept() {
+ acceptor_.asyncAccept(socket_, callback_);
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+private:
+
+ /// @brief Socket into which connection is accepted.
+ TCPSocket<SocketCallback> socket_;
+
+ /// @brief Reference to the TCPAcceptor on which asyncAccept is called.
+ TestTCPAcceptor& acceptor_;
+
+ /// @brief Instance of the callback used for asyncAccept.
+ TCPAcceptorCallback callback_;
+
+};
+
+/// @brief Pointer to the Acceptor object.
+typedef boost::shared_ptr<Acceptor> AcceptorPtr;
+
+/// @brief Test fixture class for TCPAcceptor.
+///
+/// This class provides means for creating new TCP connections, i.e. simulates
+/// clients connecting to the servers via TCPAcceptor. It is possible to create
+/// multiple simultaneous connections, which are retained by the test fixture
+/// class and closed cleanly when the test fixture is destroyed.
+class TCPAcceptorTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Besides initializing class members it also sets the test timer to guard
+ /// against endlessly running IO service when TCP connections are
+ /// unsuccessful.
+ TCPAcceptorTest()
+ : io_service_(), acceptor_(io_service_),
+ asio_endpoint_(boost::asio::ip::address::from_string(SERVER_ADDRESS),
+ SERVER_PORT),
+ endpoint_(asio_endpoint_), test_timer_(io_service_), connections_(),
+ clients_(), connections_num_(0), aborted_connections_num_(0),
+ max_connections_(1) {
+ test_timer_.setup(boost::bind(&TCPAcceptorTest::timeoutHandler, this),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ virtual ~TCPAcceptorTest() {
+ }
+
+ /// @brief Specifies how many new connections are expected before the IO
+ /// service is stopped.
+ ///
+ /// @param max_connections Connections limit.
+ void setMaxConnections(const unsigned int max_connections) {
+ max_connections_ = max_connections;
+ }
+
+ /// @brief Create ASIO endpoint from the provided endpoint by retaining the
+ /// IP address and modifying the port.
+ ///
+ /// This convenience method is useful to create new endpoint from the
+ /// existing endpoint to test reusing IP address for multiple acceptors.
+ /// The returned endpoint has the same IP address but different port.
+ ///
+ /// @param endpoint Source endpoint.
+ ///
+ /// @return New endpoint with the port number increased by 1.
+ boost::asio::ip::tcp::endpoint
+ createSiblingEndpoint(const boost::asio::ip::tcp::endpoint& endpoint) const {
+ boost::asio::ip::tcp::endpoint endpoint_copy(endpoint);
+ endpoint_copy.port(endpoint.port() + 1);
+ return (endpoint_copy);
+ }
+
+ /// @brief Opens TCP acceptor and sets 'reuse address' option.
+ void acceptorOpen() {
+ acceptor_.open(endpoint_);
+ acceptor_.setOption(TestTCPAcceptor::ReuseAddress(true));
+ }
+
+ /// @brief Starts accepting TCP connections.
+ ///
+ /// This method creates new Acceptor instance and calls accept() to start
+ /// accepting new connections. The instance of the Acceptor object is
+ /// retained in the connections_ list.
+ void accept() {
+ TCPAcceptorCallback cb = boost::bind(&TCPAcceptorTest::acceptHandler,
+ this, _1);
+ AcceptorPtr conn(new Acceptor(io_service_, acceptor_, cb));
+ connections_.push_back(conn);
+ connections_.back()->accept();
+ }
+
+ /// @brief Connect to the endpoint.
+ ///
+ /// This method creates TCPClient instance and retains it in the clients_
+ /// list.
+ void connect() {
+ TCPClientPtr client(new TCPClient(io_service_));
+ clients_.push_back(client);
+ clients_.back()->connect();
+ }
+
+ /// @brief Callback function for asynchronous accept calls.
+ ///
+ /// It stops the IO service upon error or when the number of accepted
+ /// connections reaches the max_connections_ value. Otherwise it calls
+ /// accept() to start accepting next connections.
+ ///
+ /// @param ec Error code.
+ void acceptHandler(const boost::system::error_code& ec) {
+ if (ec) {
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ ADD_FAILURE() << "error occurred while accepting connection: "
+ << ec.message();
+ } else {
+ ++aborted_connections_num_;
+ }
+ io_service_.stop();
+ }
+
+ // We have reached the maximum number of connections - end the test.
+ if (++connections_num_ >= max_connections_) {
+ io_service_.stop();
+ return;
+ }
+
+ accept();
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ void timeoutHandler() {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ io_service_.stop();
+ }
+
+ /// @brief IO service.
+ IOService io_service_;
+
+ /// @brief TCPAcceptor under test.
+ TestTCPAcceptor acceptor_;
+
+ /// @brief Server endpoint.
+ boost::asio::ip::tcp::endpoint asio_endpoint_;
+
+ /// @brief asiolink server endpoint (uses asio_endpoint_).
+ TCPEndpoint endpoint_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief List of connections on the server side.
+ std::list<AcceptorPtr> connections_;
+
+ /// @brief List of client connections.
+ std::list<TCPClientPtr> clients_;
+
+ /// @brief Current number of established connections.
+ unsigned int connections_num_;
+
+ /// @brief Current number of aborted connections.
+ unsigned int aborted_connections_num_;
+
+ /// @brief Connections limit.
+ unsigned int max_connections_;
+};
+
+// Test TCPAcceptor::asyncAccept.
+TEST_F(TCPAcceptorTest, asyncAccept) {
+ // Establish up to 10 connections.
+ setMaxConnections(10);
+
+ // Initialize acceptor.
+ acceptorOpen();
+ acceptor_.bind(endpoint_);
+ acceptor_.listen();
+
+ // Start accepting new connections.
+ accept();
+
+ // Create 10 new TCP connections (client side).
+ for (unsigned int i = 0; i < 10; ++i) {
+ connect();
+ }
+
+ // Run the IO service until we have accepted 10 connections, an error
+ // or test timeout occurred.
+ io_service_.run();
+
+ // Make sure that all accepted connections have been recorded.
+ EXPECT_EQ(10, connections_num_);
+ EXPECT_EQ(10, connections_.size());
+}
+
+// Check that it is possible to set SO_REUSEADDR flag for the TCPAcceptor.
+TEST_F(TCPAcceptorTest, reuseAddress) {
+ // We need at least two acceptors using common address. Let's create the
+ // second endpoint which has the same address but different port.
+ boost::asio::ip::tcp::endpoint asio_endpoint2(createSiblingEndpoint(asio_endpoint_));
+ TCPEndpoint endpoint2(asio_endpoint2);
+
+ // Create and open two acceptors.
+ TestTCPAcceptor acceptor1(io_service_);
+ TestTCPAcceptor acceptor2(io_service_);
+ ASSERT_NO_THROW(acceptor1.open(endpoint_));
+ ASSERT_NO_THROW(acceptor2.open(endpoint2));
+
+ // Set SO_REUSEADDR socket option so as acceptors can bind to the
+ /// same address.
+ ASSERT_NO_THROW(
+ acceptor1.setOption(TestTCPAcceptor::ReuseAddress(true))
+ );
+ ASSERT_NO_THROW(
+ acceptor2.setOption(TestTCPAcceptor::ReuseAddress(true))
+ );
+ ASSERT_NO_THROW(acceptor1.bind(endpoint_));
+ ASSERT_NO_THROW(acceptor2.bind(endpoint2));
+
+ // Create third acceptor, but don't set the SO_REUSEADDR. It should
+ // refuse to bind.
+ TCPEndpoint endpoint3(createSiblingEndpoint(asio_endpoint2));
+ TestTCPAcceptor acceptor3(io_service_);
+ ASSERT_NO_THROW(acceptor3.open(endpoint3));
+ EXPECT_THROW(acceptor3.bind(endpoint_), boost::system::system_error);
+}
+
+// Test that TCPAcceptor::getProtocol returns IPPROTO_TCP.
+TEST_F(TCPAcceptorTest, getProtocol) {
+ EXPECT_EQ(IPPROTO_TCP, acceptor_.getProtocol());
+}
+
+// Test that TCPAcceptor::getNative returns valid socket descriptor.
+TEST_F(TCPAcceptorTest, getNative) {
+ // Initially the descriptor should be invalid (negative).
+ ASSERT_LT(acceptor_.getNative(), 0);
+ // Now open the socket and make sure the returned descriptor is now valid.
+ ASSERT_NO_THROW(acceptorOpen());
+ EXPECT_GE(acceptor_.getNative(), 0);
+}
+
+// macOS 10.12.3 has a bug which causes the connections to not enter
+// the TIME-WAIT state and they never get closed.
+#if !defined (OS_OSX)
+
+// Test that TCPAcceptor::close works properly.
+TEST_F(TCPAcceptorTest, close) {
+ // Initialize acceptor.
+ acceptorOpen();
+ acceptor_.bind(endpoint_);
+ acceptor_.listen();
+
+ // Start accepting new connections.
+ accept();
+
+ // Create 10 new TCP connections (client side).
+ for (unsigned int i = 0; i < 10; ++i) {
+ connect();
+ }
+
+ // Close the acceptor before connections are accepted.
+ acceptor_.close();
+
+ // Run the IO service.
+ io_service_.run();
+
+ // The connections should have been aborted.
+ EXPECT_EQ(1, connections_num_);
+ EXPECT_EQ(1, aborted_connections_num_);
+ EXPECT_EQ(1, connections_.size());
+}
+
+#endif
+
+}
diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc
index 15bb779ce5..e247722099 100644
--- a/src/lib/asiolink/tests/tcp_socket_unittest.cc
+++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -22,14 +22,15 @@
#include <boost/shared_ptr.hpp>
#include <gtest/gtest.h>
-#include <string>
+#include <algorithm>
#include <arpa/inet.h>
+#include <cstddef>
+#include <cstdlib>
+#include <errno.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
-#include <algorithm>
-#include <cstdlib>
-#include <cstddef>
+#include <string>
#include <vector>
using namespace boost::asio;
@@ -62,7 +63,7 @@ public:
NONE = 4 ///< "Not set" state
};
- /// \brief Minimim size of buffers
+ /// \brief Minimum size of buffers
enum {
MIN_SIZE = (64 * 1024 + 2) ///< 64kB + two bytes for a count
};
@@ -149,7 +150,7 @@ public:
return (ptr_->expected_);
}
- /// \brief Get offset intodData
+ /// \brief Get offset into data
size_t& offset() {
return (ptr_->offset_);
}
@@ -355,7 +356,15 @@ TEST(TCPSocket, sequenceTest) {
EXPECT_EQ(0, server_cb.getCode());
EXPECT_EQ(TCPCallback::OPEN, client_cb.called());
- EXPECT_EQ(0, client_cb.getCode());
+
+ // On some operating system the async_connect may return EINPROGRESS.
+ // This doesn't necessarily indicate an error. In most cases trying
+ // to asynchronously write and read from the socket would work just
+ // fine.
+ if ((client_cb.getCode()) != 0 && (client_cb.getCode() != EINPROGRESS)) {
+ ADD_FAILURE() << "expected error code of 0 or " << EINPROGRESS
+ << " as a result of async_connect, got " << client_cb.getCode();
+ }
// Step 2. Get the client to write to the server asynchronously. The
// server will loop reading the data synchronously.
@@ -418,7 +427,7 @@ TEST(TCPSocket, sequenceTest) {
// Run the callbacks. Several options are possible depending on how ASIO
// is implemented and whether the message gets fragmented:
//
- // 1) The send handler may complete immediately, regardess of whether the
+ // 1) The send handler may complete immediately, regardless of whether the
// data has been read by the client. (This is the most likely.)
// 2) The send handler may only run after all the data has been read by
// the client. (This could happen if the client's TCP buffers were too
diff --git a/src/lib/asiolink/tests/unix_domain_socket_unittest.cc b/src/lib/asiolink/tests/unix_domain_socket_unittest.cc
new file mode 100644
index 0000000000..62c16c9f3a
--- /dev/null
+++ b/src/lib/asiolink/tests/unix_domain_socket_unittest.cc
@@ -0,0 +1,313 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/unix_domain_socket.h>
+#include <asiolink/testutils/test_server_unix_socket.h>
+#include <gtest/gtest.h>
+#include <array>
+#include <cstdio>
+#include <cstdlib>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test unix socket file name.
+const std::string TEST_SOCKET = "test-socket";
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for @ref UnixDomainSocket class.
+class UnixDomainSocketTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Removes unix socket descriptor before the test.
+ UnixDomainSocketTest() :
+ io_service_(),
+ test_socket_(new test::TestServerUnixSocket(io_service_,
+ unixSocketFilePath())),
+ response_(),
+ read_buf_() {
+ test_socket_->startTimer(TEST_TIMEOUT);
+ removeUnixSocketFile();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes unix socket descriptor after the test.
+ virtual ~UnixDomainSocketTest() {
+ removeUnixSocketFile();
+ }
+
+ /// @brief Returns socket file path.
+ ///
+ /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
+ /// socket file is created in the location pointed to by this variable.
+ /// Otherwise, it is created in the build directory.
+ static std::string unixSocketFilePath() {
+ std::ostringstream s;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ s << std::string(env);
+ } else {
+ s << TEST_DATA_BUILDDIR;
+ }
+
+ s << "/" << TEST_SOCKET;
+ return (s.str());
+ }
+
+ /// @brief Removes unix socket descriptor.
+ void removeUnixSocketFile() {
+ static_cast<void>(remove(unixSocketFilePath().c_str()));
+ }
+
+ /// @brief Performs asynchronous receive on unix domain socket.
+ ///
+ /// This function performs partial read from the unix domain socket.
+ /// It uses @c read_buf_ or small size to ensure that the buffer fills
+ /// in before all that have been read. The partial responses are
+ /// appended to the @c response_ class member.
+ ///
+ /// If the response received so far is shorter than the expected
+ /// response, another partial read is scheduled.
+ ///
+ /// @param socket Reference to the unix domain socket.
+ /// @param expected_response Expected response.
+ void doReceive(UnixDomainSocket& socket,
+ const std::string& expected_response) {
+ socket.asyncReceive(&read_buf_[0], read_buf_.size(),
+ [this, &socket, expected_response]
+ (const boost::system::error_code& ec, size_t length) {
+ if (!ec) {
+ // Append partial response received and see if the
+ // size of the response received so far is still
+ // smaller than expected. If it is, schedule another
+ // partial read.
+ response_.append(&read_buf_[0], length);
+ if (expected_response.size() > response_.size()) {
+ doReceive(socket, expected_response);
+ }
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ ADD_FAILURE() << "error occurred while asynchronously receiving"
+ " data via unix domain socket: " << ec.message();
+ }
+ });
+ }
+
+ /// @brief IO service used by the tests.
+ IOService io_service_;
+
+ /// @brief Server side unix socket used in these tests.
+ test::TestServerUnixSocketPtr test_socket_;
+
+ /// @brief String containing a response received with @c doReceive.
+ std::string response_;
+
+ /// @brief Read buffer used by @c doReceive.
+ std::array<char, 2> read_buf_;
+};
+
+// This test verifies that the client can send data over the unix
+// domain socket and receive a response.
+TEST_F(UnixDomainSocketTest, sendReceive) {
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Setup client side.
+ UnixDomainSocket socket(io_service_);
+ ASSERT_NO_THROW(socket.connect(unixSocketFilePath()));
+
+ // Send "foo".
+ const std::string outbound_data = "foo";
+ size_t sent_size = 0;
+ ASSERT_NO_THROW(sent_size = socket.write(outbound_data.c_str(),
+ outbound_data.size()));
+ // Make sure all data have been sent.
+ ASSERT_EQ(outbound_data.size(), sent_size);
+
+ // Run IO service to generate server's response.
+ while ((test_socket_->getResponseNum() < 1) &&
+ (!test_socket_->isStopped())) {
+ io_service_.run_one();
+ }
+
+ // Receive response from the socket.
+ std::array<char, 1024> read_buf;
+ size_t bytes_read = 0;
+ ASSERT_NO_THROW(bytes_read = socket.receive(&read_buf[0], read_buf.size()));
+ std::string response(&read_buf[0], bytes_read);
+
+ // The server should prepend "received" to the data we had sent.
+ EXPECT_EQ("received foo", response);
+}
+
+// This test verifies that the client can send the data over the unix
+// domain socket and receive a response asynchronously.
+TEST_F(UnixDomainSocketTest, asyncSendReceive) {
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Setup client side.
+ UnixDomainSocket socket(io_service_);
+
+ // We're going to asynchronously connect to the server. The boolean value
+ // below will be modified by the connect handler function (lambda) invoked
+ // when the connection is established or if an error occurs.
+ bool connect_handler_invoked = false;
+ ASSERT_NO_THROW(socket.asyncConnect(unixSocketFilePath(),
+ [this, &connect_handler_invoked](const boost::system::error_code& ec) {
+ // Indicate that the handler has been called so as the loop below gets
+ // interrupted.
+ connect_handler_invoked = true;
+ // Operation aborted indicates that IO service has been stopped. This
+ // shouldn't happen here.
+ if (ec && (ec.value() != boost::asio::error::operation_aborted)) {
+ ADD_FAILURE() << "error occurred while asynchronously connecting"
+ " via unix domain socket: " << ec.message();
+ }
+ }
+ ));
+ // Run IO service until connect handler is invoked.
+ while (!connect_handler_invoked && (!test_socket_->isStopped())) {
+ io_service_.run_one();
+ }
+
+ // We are going to asynchronously send the 'foo' over the unix socket.
+ const std::string outbound_data = "foo";
+ size_t sent_size = 0;
+ ASSERT_NO_THROW(socket.asyncSend(outbound_data.c_str(), outbound_data.size(),
+ [this, &sent_size](const boost::system::error_code& ec, size_t length) {
+ // If we have been successful sending the data, record the number of
+ // bytes we have sent.
+ if (!ec) {
+ sent_size = length;
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ ADD_FAILURE() << "error occurred while asynchronously sending the"
+ " data over unix domain socket: " << ec.message();
+ }
+ }
+ ));
+
+ // Run IO service to generate server's response.
+ while ((test_socket_->getResponseNum() < 1) &&
+ (!test_socket_->isStopped())) {
+ io_service_.run_one();
+ }
+
+ // There is no guarantee that all data have been sent so we only check that
+ // some data have been sent.
+ ASSERT_GT(sent_size, 0);
+
+ std::string expected_response = "received foo";
+ doReceive(socket, expected_response);
+
+ // Run IO service until we get the full response from the server.
+ while ((response_.size() < expected_response.size()) &&
+ !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+
+ // Check that the entire response has been received and is correct.
+ EXPECT_EQ(expected_response, response_);
+}
+
+// This test verifies that UnixDomainSocketError exception is thrown
+// on attempt to connect, write or receive when the server socket
+// is not available.
+TEST_F(UnixDomainSocketTest, clientErrors) {
+ UnixDomainSocket socket(io_service_);
+ ASSERT_THROW(socket.connect(unixSocketFilePath()), UnixDomainSocketError);
+ const std::string outbound_data = "foo";
+ ASSERT_THROW(socket.write(outbound_data.c_str(), outbound_data.size()),
+ UnixDomainSocketError);
+ std::array<char, 1024> read_buf;
+ ASSERT_THROW(socket.receive(&read_buf[0], read_buf.size()),
+ UnixDomainSocketError);
+}
+
+// This test verifies that an error is returned on attempt to asynchronously
+// connect, write or receive when the server socket is not available.
+TEST_F(UnixDomainSocketTest, asyncClientErrors) {
+ UnixDomainSocket socket(io_service_);
+
+ // Asynchronous operations signal errors through boost::system::error_code
+ // object passed to the handler function. This object casts to boolean.
+ // In case of success the object casts to false. In case of an error it
+ // casts to true. The actual error codes can be retrieved by comparing the
+ // ec objects to predefined error objects. We don't check for the actual
+ // errors here, because it is not certain that the same error codes would
+ // be returned on various operating systems.
+
+ // In the following tests we use C++11 lambdas as callbacks.
+
+ // Connect
+ bool connect_handler_invoked = false;
+ socket.asyncConnect(unixSocketFilePath(),
+ [this, &connect_handler_invoked](const boost::system::error_code& ec) {
+ connect_handler_invoked = true;
+ EXPECT_TRUE(ec);
+ });
+ while (!connect_handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+
+ // Send
+ const std::string outbound_data = "foo";
+ bool send_handler_invoked = false;
+ socket.asyncSend(outbound_data.c_str(), outbound_data.size(),
+ [this, &send_handler_invoked]
+ (const boost::system::error_code& ec, size_t length) {
+ send_handler_invoked = true;
+ EXPECT_TRUE(ec);
+ });
+ while (!send_handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+
+ // Receive
+ bool receive_handler_invoked = false;
+ std::array<char, 1024> read_buf;
+ socket.asyncReceive(&read_buf[0], read_buf.size(),
+ [this, &receive_handler_invoked]
+ (const boost::system::error_code& ec, size_t length) {
+ receive_handler_invoked = true;
+ EXPECT_TRUE(ec);
+ });
+ while (!receive_handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+// Check that native socket descriptor is returned correctly when
+// the socket is connected.
+TEST_F(UnixDomainSocketTest, getNative) {
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Setup client side.
+ UnixDomainSocket socket(io_service_);
+ ASSERT_NO_THROW(socket.connect(unixSocketFilePath()));
+ ASSERT_GE(socket.getNative(), 0);
+}
+
+// Check that protocol returned is 0.
+TEST_F(UnixDomainSocketTest, getProtocol) {
+ UnixDomainSocket socket(io_service_);
+ EXPECT_EQ(0, socket.getProtocol());
+}
+
+}
diff --git a/src/lib/asiolink/testutils/Makefile.am b/src/lib/asiolink/testutils/Makefile.am
new file mode 100644
index 0000000000..ec4d1815b8
--- /dev/null
+++ b/src/lib/asiolink/testutils/Makefile.am
@@ -0,0 +1,24 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libasiolinktest.la
+
+libasiolinktest_la_SOURCES = test_server_unix_socket.cc test_server_unix_socket.h
+
+libasiolinktest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libasiolinktest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libasiolinktest_la_LDFLAGS = $(AM_LDFLAGS)
+
+libasiolinktest_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libasiolinktest_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libasiolinktest_la_LIBADD += $(BOOST_LIBS)
+
+endif
diff --git a/src/lib/asiolink/testutils/test_server_unix_socket.cc b/src/lib/asiolink/testutils/test_server_unix_socket.cc
new file mode 100644
index 0000000000..c72b1270d7
--- /dev/null
+++ b/src/lib/asiolink/testutils/test_server_unix_socket.cc
@@ -0,0 +1,317 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/testutils/test_server_unix_socket.h>
+#include <boost/bind.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <set>
+#include <sstream>
+
+using namespace boost::asio::local;
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief ASIO unix domain socket.
+typedef stream_protocol::socket UnixSocket;
+
+/// @brief Pointer to the ASIO unix domain socket.
+typedef boost::shared_ptr<UnixSocket> UnixSocketPtr;
+
+/// @brief Callback function invoked when response is sent from the server.
+typedef std::function<void()> SentResponseCallback;
+
+/// @brief Connection to the server over unix domain socket.
+///
+/// It reads the data over the socket, sends responses and closes a socket.
+class Connection : public boost::enable_shared_from_this<Connection> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// It starts asynchronous read operation.
+ ///
+ /// @param unix_socket Pointer to the unix domain socket into which
+ /// connection has been accepted.
+ /// @param custom_response Custom response that the server should send.
+ /// @param sent_response_callback Callback function to be invoked when
+ /// server sends a response.
+ Connection(const UnixSocketPtr& unix_socket,
+ const std::string custom_response,
+ SentResponseCallback sent_response_callback)
+ : socket_(unix_socket), custom_response_(custom_response),
+ sent_response_callback_(sent_response_callback) {
+ }
+
+ /// @brief Starts asynchronous read from the socket.
+ void start() {
+ socket_->async_read_some(boost::asio::buffer(&raw_buf_[0], raw_buf_.size()),
+ boost::bind(&Connection::readHandler, shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ }
+
+ /// @brief Closes the socket.
+ void stop() {
+ socket_->close();
+ }
+
+ /// @brief Handler invoked when data have been received over the socket.
+ ///
+ /// This is the handler invoked when the data have been received over the
+ /// socket. If custom response has been specified, this response is sent
+ /// back to the client. Otherwise, the handler echoes back the request
+ /// and prepends the word "received ". Finally, it calls a custom
+ /// callback function (specified in the constructor) to notify that the
+ /// response has been sent over the socket.
+ ///
+ /// @param bytes_transferred Number of bytes received.
+ void
+ readHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred) {
+ // This is most likely due to the abort.
+ if (ec) {
+ return;
+ }
+
+ if (!custom_response_.empty()) {
+ boost::asio::write(*socket_,
+ boost::asio::buffer(custom_response_.c_str(), custom_response_.size()));
+
+ } else {
+ std::string received(&raw_buf_[0], bytes_transferred);
+ std::string response("received " + received);
+ boost::asio::write(*socket_,
+ boost::asio::buffer(response.c_str(), response.size()));
+ }
+
+ start();
+
+ // Invoke callback function to notify that the response has been sent.
+ sent_response_callback_();
+ }
+
+private:
+
+ /// @brief Pointer to the unix domain socket.
+ UnixSocketPtr socket_;
+
+ /// @brief Custom response to be sent to the client.
+ std::string custom_response_;
+
+ /// @brief Receive buffer.
+ std::array<char, 1024> raw_buf_;
+
+ /// @brief Pointer to the callback function to be invoked when response
+ /// has been sent.
+ SentResponseCallback sent_response_callback_;
+
+};
+
+/// @brief Pointer to a Connection object.
+typedef boost::shared_ptr<Connection> ConnectionPtr;
+
+/// @brief Connection pool.
+///
+/// Holds all connections established with the server and gracefully
+/// terminates these connections.
+class ConnectionPool {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ ConnectionPool(IOService& io_service)
+ : io_service_(io_service), connections_(), next_socket_(),
+ response_num_(0) {
+ }
+
+ /// @brief Destructor.
+ ~ConnectionPool() {
+ stopAll();
+ }
+
+ /// @brief Creates new unix domain socket and returns it.
+ ///
+ /// This convenience method creates a socket which can be used to accept
+ /// new connections. If such socket already exists, it is returned.
+ ///
+ /// @return Pointer to the socket.
+ UnixSocketPtr getSocket() {
+ if (!next_socket_) {
+ next_socket_.reset(new UnixSocket(io_service_.get_io_service()));
+ }
+ return (next_socket_);
+ }
+
+ /// @brief Starts new connection.
+ ///
+ /// The socket returned by the @ref ConnectionPool::getSocket is used to
+ /// create new connection. Then, the @ref next_socket_ is reset, to force
+ /// the @ref ConnectionPool::getSocket to generate a new socket for a
+ /// next connection.
+ ///
+ /// @param custom_response Custom response to be sent to the client.
+ void start(const std::string& custom_response) {
+ ConnectionPtr conn(new Connection(next_socket_, custom_response, [this] {
+ ++response_num_;
+ }));
+ conn->start();
+
+ connections_.insert(conn);
+ next_socket_.reset();
+ }
+
+ /// @brief Stops the given connection.
+ ///
+ /// @param conn Pointer to the connection to be stopped.
+ void stop(const ConnectionPtr& conn) {
+ conn->stop();
+ connections_.erase(conn);
+ }
+
+ /// @brief Stops all connections.
+ void stopAll() {
+ for (auto conn = connections_.begin(); conn != connections_.end();
+ ++conn) {
+ (*conn)->stop();
+ }
+ connections_.clear();
+ }
+
+ /// @brief Returns number of responses sent so far.
+ size_t getResponseNum() const {
+ return (response_num_);
+ }
+
+private:
+
+ /// @brief Reference to the IO service.
+ IOService& io_service_;
+
+ /// @brief Container holding established connections.
+ std::set<ConnectionPtr> connections_;
+
+ /// @brief Holds pointer to the generated socket.
+ ///
+ /// This socket will be used by the next connection.
+ UnixSocketPtr next_socket_;
+
+ /// @brief Holds the number of sent responses.
+ size_t response_num_;
+};
+
+
+TestServerUnixSocket::TestServerUnixSocket(IOService& io_service,
+ const std::string& socket_file_path,
+ const std::string& custom_response)
+ : io_service_(io_service),
+ server_endpoint_(socket_file_path),
+ server_acceptor_(io_service_.get_io_service()),
+ test_timer_(io_service_),
+ custom_response_(custom_response),
+ connection_pool_(new ConnectionPool(io_service)),
+ stopped_(false),
+ running_(false) {
+}
+
+TestServerUnixSocket::~TestServerUnixSocket() {
+ server_acceptor_.close();
+}
+
+void
+TestServerUnixSocket::generateCustomResponse(const uint64_t response_size) {
+ std::ostringstream s;
+ s << "{";
+ while (s.tellp() < response_size) {
+ s << "\"param\": \"value\",";
+ }
+ s << "}";
+ custom_response_ = s.str();
+}
+
+void
+TestServerUnixSocket::startTimer(const long test_timeout) {
+ test_timer_.setup(boost::bind(&TestServerUnixSocket::timeoutHandler, this),
+ test_timeout, IntervalTimer::ONE_SHOT);
+}
+
+void
+TestServerUnixSocket::stopServer() {
+ test_timer_.cancel();
+ server_acceptor_.cancel();
+ connection_pool_->stopAll();
+}
+
+void
+TestServerUnixSocket::bindServerSocket(const bool use_thread) {
+ server_acceptor_.open();
+ server_acceptor_.bind(server_endpoint_);
+ server_acceptor_.listen();
+ accept();
+
+ // When threads are in use, we need to post a handler which will be invoked
+ // when the thread has already started and the IO service is running. The
+ // main thread can move forward when it receives this signal from the handler.
+ if (use_thread) {
+ io_service_.post(boost::bind(&TestServerUnixSocket::signalRunning,
+ this));
+ }
+}
+
+void
+TestServerUnixSocket::acceptHandler(const boost::system::error_code& ec) {
+ if (ec) {
+ return;
+ }
+
+ connection_pool_->start(custom_response_);
+ accept();
+}
+
+void
+TestServerUnixSocket::accept() {
+ server_acceptor_.async_accept(*(connection_pool_->getSocket()),
+ boost::bind(&TestServerUnixSocket::acceptHandler, this,
+ boost::asio::placeholders::error));
+}
+
+void
+TestServerUnixSocket::signalRunning() {
+ {
+ isc::util::thread::Mutex::Locker lock(mutex_);
+ running_ = true;
+ }
+ condvar_.signal();
+}
+
+void
+TestServerUnixSocket::waitForRunning() {
+ isc::util::thread::Mutex::Locker lock(mutex_);
+ while (!running_) {
+ condvar_.wait(mutex_);
+ }
+}
+
+void
+TestServerUnixSocket::timeoutHandler() {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ io_service_.stop();
+ stopped_ = true;
+}
+
+size_t
+TestServerUnixSocket::getResponseNum() const {
+ return (connection_pool_->getResponseNum());
+}
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/testutils/test_server_unix_socket.h b/src/lib/asiolink/testutils/test_server_unix_socket.h
new file mode 100644
index 0000000000..3deb59fd96
--- /dev/null
+++ b/src/lib/asiolink/testutils/test_server_unix_socket.h
@@ -0,0 +1,171 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TEST_SERVER_UNIX_SOCKET_H
+#define TEST_SERVER_UNIX_SOCKET_H
+
+#include <config.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <util/threads/thread.h>
+#include <util/threads/sync.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <list>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+class ConnectionPool;
+
+/// @brief Provides unix domain socket functionality for unit tests.
+///
+/// This class represents a server side socket. It can be used to
+/// test client's transmission over the unix domain socket. By default,
+/// the server side socket echoes the client's message so the client's
+/// message (prefixed with the word "received").
+///
+/// It is also possible to specify a custom response from the server
+/// instead of echoing back the request.
+///
+/// It is possible to make multiple connections to the server side
+/// socket simultaneously.
+///
+/// The test should perform IOService::run_one until it finds that
+/// the number of responses sent by the server is greater than
+/// expected. The number of responses sent so far can be retrieved
+/// using @ref TestServerUnixSocket::getResponseNum.
+///
+/// This class uses @c shared_from_this() to pass its instance to the
+/// @c boost::bind function, thus the caller must store shared pointer
+/// to this object.
+class TestServerUnixSocket {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service.
+ /// @param socket_file_path Socket file path.
+ /// @param custom_response Custom response to be sent to the client.
+ TestServerUnixSocket(IOService& io_service,
+ const std::string& socket_file_path,
+ const std::string& custom_response = "");
+
+ /// @brief Destructor.
+ ///
+ /// Closes active connections.
+ ~TestServerUnixSocket();
+
+ /// @brief Starts timer for detecting test timeout.
+ ///
+ /// @param test_timeout Test timeout in milliseconds.
+ void startTimer(const long test_timeout);
+
+ /// @brief Cancels all asynchronous operations.
+ void stopServer();
+
+ /// @brief Generates response of a given length.
+ ///
+ /// Note: The response may be a few bytes larger than requested.
+ ///
+ /// @param response_size Desired response size.
+ void generateCustomResponse(const uint64_t response_size);
+
+ /// @brief Creates and binds server socket.
+ ///
+ /// @param use_thread Boolean value indicating if the IO service
+ /// is running in thread.
+ void bindServerSocket(const bool use_thread = false);
+
+ /// @brief Server acceptor handler.
+ ///
+ /// @param ec Error code.
+ void acceptHandler(const boost::system::error_code& ec);
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ void timeoutHandler();
+
+ /// @brief Return number of responses sent so far to the clients.
+ size_t getResponseNum() const;
+
+ /// @brief Indicates if the server has been stopped.
+ bool isStopped() {
+ return (stopped_);
+ }
+
+ /// @brief Waits for the server signal that it is running.
+ ///
+ /// When the caller starts the service he indicates whether
+ /// IO service will be running in thread or not. If threads
+ /// are used the caller has to wait for the IO service to
+ /// actually run. In such case this function should be invoked
+ /// which waits for a posted callback to be executed. When this
+ /// happens it means that IO service is running and the main
+ /// thread can move forward.
+ void waitForRunning();
+
+private:
+
+ /// @brief Asynchronously accept new connections.
+ void accept();
+
+ /// @brief Handler invoked to signal that server is running.
+ ///
+ /// This is used only when thread is used to run IO service.
+ void signalRunning();
+
+ /// @brief IO service used by the tests.
+ IOService& io_service_;
+
+ /// @brief Server endpoint.
+ boost::asio::local::stream_protocol::endpoint server_endpoint_;
+ /// @brief Server acceptor.
+ boost::asio::local::stream_protocol::acceptor server_acceptor_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Holds custom response to be sent to the client.
+ std::string custom_response_;
+
+ /// @brief Pool of connections.
+ boost::shared_ptr<ConnectionPool> connection_pool_;
+
+ /// @brief Indicates if IO service has been stopped as a result of
+ /// a timeout.
+ bool stopped_;
+
+ /// @brief Indicates if the server in a thread is running.
+ bool running_;
+
+ /// @brief Mutex used by the server.
+ ///
+ /// Mutex is used in situations when server's IO service is being run in a
+ /// thread to synchronize this thread with a main thread using
+ /// @ref signalRunning and @ref waitForRunning.
+ isc::util::thread::Mutex mutex_;
+
+ /// @brief Conditional variable used by the server.
+ ///
+ /// Conditional variable is used in situations when server's IO service is
+ /// being run in a thread to synchronize this thread with a main thread
+ /// using @ref signalRunning and @ref waitForRunning.
+ isc::util::thread::CondVar condvar_;
+};
+
+/// @brief Pointer to the @ref TestServerUnixSocket.
+typedef boost::shared_ptr<TestServerUnixSocket> TestServerUnixSocketPtr;
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // TEST_SERVER_UNIX_SOCKET_H
diff --git a/src/lib/asiolink/unix_domain_socket.cc b/src/lib/asiolink/unix_domain_socket.cc
new file mode 100644
index 0000000000..44accc3854
--- /dev/null
+++ b/src/lib/asiolink/unix_domain_socket.cc
@@ -0,0 +1,364 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/unix_domain_socket.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <iostream>
+using namespace boost::asio::local;
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Implementation of the unix domain socket.
+class UnixDomainSocketImpl : public boost::enable_shared_from_this<UnixDomainSocketImpl> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the socket class.
+ UnixDomainSocketImpl(IOService& io_service)
+ : socket_(io_service.get_io_service()) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the socket.
+ ~UnixDomainSocketImpl() {
+ close();
+ }
+
+ /// @brief Asynchronously connects to an endpoint.
+ ///
+ /// This method schedules asynchronous connect and installs the
+ /// @ref UnixDomainSocketImpl::connectHandler as a callback.
+ ///
+ /// @param endpoint Reference to an endpoint to connect to.
+ /// @param handler User supplied handler to be invoked when the connection
+ /// is established or when error is signalled.
+ void asyncConnect(const stream_protocol::endpoint& endpoint,
+ const UnixDomainSocket::ConnectHandler& handler);
+
+ /// @brief Local handler invoked as a result of asynchronous connection.
+ ///
+ /// This is a wrapper around the user supplied callback. It ignores
+ /// EINPROGRESS errors which are observed on some operating systems as
+ /// a result of trying to connect asynchronously. This error code doesn't
+ /// necessarily indicate a problem and the subsequent attempts to read
+ /// and write to the socket will succeed. Therefore, the handler simply
+ /// overrides this error code with success status. The user supplied
+ /// handler doesn't need to deal with the EINPROGRESS error codes.
+ ///
+ /// @param remote_handler User supplied callback.
+ /// @param ec Error code returned as a result of connection.
+ void connectHandler(const UnixDomainSocket::ConnectHandler& remote_handler,
+ const boost::system::error_code& ec);
+
+ /// @brief Asynchronously sends data over the socket.
+ ///
+ /// This method schedules an asynchronous send and installs the
+ /// @ref UnixDomainSocketImpl::sendHandler as a callback.
+ ///
+ /// @param data Pointer to data to be sent.
+ /// @param length Number of bytes to be sent.
+ /// @param handler Callback to be invoked when data have been sent or an
+ /// sending error is signalled.
+ void asyncSend(const void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler);
+
+ /// @brief Asynchronously sends the data over the socket.
+ ///
+ /// This method is called by the @ref asyncSend and the @ref sendHandler
+ /// if the asynchronous send has to be repeated as a result of receiving
+ /// EAGAIN or EWOULDBLOCK.
+ ///
+ /// @param buffer Buffers holding the data to be sent.
+ /// @param handler User supplied callback to be invoked when data have
+ /// been sent or sending error is signalled.
+ void doSend(const boost::asio::const_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler);
+
+
+ /// @brief Local handler invoked as a result of asynchronous send.
+ ///
+ /// This handler is invoked as a result of asynchronous send. It is a
+ /// wrapper callback around the user supplied callback. It handles
+ /// EWOULDBLOCK and EAGAIN errors by retrying an asynchronous send.
+ /// These errors are often returned on some operating systems, even
+ /// though one would expect that asynchronous operation would not
+ /// return such errors. Because these errors are handled by the
+ /// wrapper callback, the user supplied callback never receives
+ /// these errors.
+ ///
+ /// @param remote_handler User supplied callback.
+ /// @param buffer Buffers holding the data to be sent.
+ /// @param ec Error code returned as a result of sending the data.
+ /// @param length Length of the data sent.
+ void sendHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::const_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length);
+
+ /// @brief Asynchronously receive data over the socket.
+ ///
+ /// This method schedules asynchronous receive and installs the
+ /// @ref UnixDomainSocketImpl::receiveHandler is a callback.
+ ///
+ /// @param data Pointer to a buffer into which the data should be read.
+ /// @param length Length of the buffer.
+ /// @param handler User supplied callback invoked when data have been
+ /// received or an error is signalled.
+ void asyncReceive(void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler);
+
+ /// @brief Asynchronously receives the data over the socket.
+ ///
+ /// This method is called @ref asyncReceive and @ref receiveHandler when
+ /// EWOULDBLOCK or EAGAIN is returned.
+ ///
+ /// @param buffer A buffer into which the data should be received.
+ /// @param handler User supplied callback invoked when data have been
+ /// received on an error is signalled.
+ void doReceive(const boost::asio::mutable_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler);
+
+ /// @brief Local handler invoked as a result of asynchronous receive.
+ ///
+ /// This handler is invoked as a result of asynchronous receive. It is a
+ /// wrapper callback around the user supplied callback. It handles
+ /// EWOULDBLOCK and EAGAIN by retrying to asynchronously receive the
+ /// data. These errors are often returned on some operating systems, even
+ /// though one would expect that asynchronous operation would not
+ /// return such errors. Because these errors are handled by the
+ /// wrapper callback, the user supplied callback never receives
+ /// these errors.
+ ///
+ /// @param remote_handler User supplied callback.
+ /// @param buffer Buffer into which the data are received.
+ /// @param ec Error code returned as a result of asynchronous receive.
+ /// @param length Size of the received data.
+ void receiveHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::mutable_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length);
+
+ /// @brief Disables read and write operations on the socket.
+ void shutdown();
+
+ /// @brief Cancels asynchronous operations on the socket.
+ void cancel();
+
+ /// @brief Closes the socket.
+ void close();
+
+ /// @brief Instance of the boost asio unix domain socket.
+ stream_protocol::socket socket_;
+};
+
+void
+UnixDomainSocketImpl::asyncConnect(const stream_protocol::endpoint& endpoint,
+ const UnixDomainSocket::ConnectHandler& handler) {
+ using namespace std::placeholders;
+
+ UnixDomainSocket::ConnectHandler local_handler =
+ std::bind(&UnixDomainSocketImpl::connectHandler, shared_from_this(),
+ handler, _1);
+ socket_.async_connect(endpoint, local_handler);
+}
+
+void
+UnixDomainSocketImpl::connectHandler(const UnixDomainSocket::ConnectHandler& remote_handler,
+ const boost::system::error_code& ec) {
+ // It was observed on Debian and Fedora that asynchronous connect may result
+ // in EINPROGRESS error. This doesn't really indicate a problem with a
+ // connection. If we continue transmitting data over the socket it will
+ // succeed. So we suppress this error and return 'success' to the user's
+ // handler.
+ if (ec.value() == boost::asio::error::in_progress) {
+ remote_handler(boost::system::error_code());
+ } else {
+ remote_handler(ec);
+ }
+}
+
+void
+UnixDomainSocketImpl::asyncSend(const void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler) {
+ doSend(boost::asio::buffer(data, length), handler);
+}
+
+void
+UnixDomainSocketImpl::doSend(const boost::asio::const_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler) {
+ using namespace std::placeholders;
+
+ UnixDomainSocket::Handler local_handler =
+ std::bind(&UnixDomainSocketImpl::sendHandler, shared_from_this(),
+ handler, buffer, _1, _2);
+ socket_.async_send(buffer, local_handler);
+}
+
+void
+UnixDomainSocketImpl::sendHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::const_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length) {
+ // The asynchronous send may return EWOULDBLOCK or EAGAIN on some
+ // operating systems. In this case, we simply retry hoping that it
+ // will succeed next time. The user's callback never sees these
+ // errors.
+ if ((ec.value() == boost::asio::error::would_block) ||
+ (ec.value() == boost::asio::error::try_again)) {
+ doSend(buffer, remote_handler);
+ }
+ remote_handler(ec, length);
+}
+
+void
+UnixDomainSocketImpl::asyncReceive(void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler) {
+ doReceive(boost::asio::buffer(data, length), handler);
+}
+
+void
+UnixDomainSocketImpl::doReceive(const boost::asio::mutable_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler) {
+ using namespace std::placeholders;
+
+ UnixDomainSocket::Handler local_handler =
+ std::bind(&UnixDomainSocketImpl::receiveHandler, shared_from_this(),
+ handler, buffer, _1, _2);
+ socket_.async_receive(buffer, 0, local_handler);
+}
+
+void
+UnixDomainSocketImpl::receiveHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::mutable_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length) {
+ // The asynchronous receive may return EWOULDBLOCK or EAGAIN on some
+ // operating systems. In this case, we simply retry hoping that it
+ // will succeed next time. The user's callback never sees these
+ // errors.
+ if ((ec.value() == boost::asio::error::would_block) ||
+ (ec.value() == boost::asio::error::try_again)) {
+ doReceive(buffer, remote_handler);
+ }
+ remote_handler(ec, length);
+}
+
+void
+UnixDomainSocketImpl::shutdown() {
+ boost::system::error_code ec;
+ static_cast<void>(socket_.shutdown(stream_protocol::socket::shutdown_both, ec));
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+void
+UnixDomainSocketImpl::cancel() {
+ boost::system::error_code ec;
+ static_cast<void>(socket_.cancel(ec));
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+void
+UnixDomainSocketImpl::close() {
+ boost::system::error_code ec;
+ static_cast<void>(socket_.close(ec));
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+UnixDomainSocket::UnixDomainSocket(IOService& io_service)
+ : impl_(new UnixDomainSocketImpl(io_service)) {
+}
+
+int
+UnixDomainSocket::getNative() const {
+ return (impl_->socket_.native());
+}
+
+int
+UnixDomainSocket::getProtocol() const {
+ return (0);
+}
+
+void
+UnixDomainSocket::connect(const std::string& path) {
+ boost::system::error_code ec;
+ impl_->socket_.connect(stream_protocol::endpoint(path.c_str()), ec);
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+void
+UnixDomainSocket::asyncConnect(const std::string& path, const ConnectHandler& handler) {
+ impl_->asyncConnect(stream_protocol::endpoint(path.c_str()), handler);
+}
+
+size_t
+UnixDomainSocket::write(const void* data, size_t length) {
+ boost::system::error_code ec;
+ size_t res = boost::asio::write(impl_->socket_,
+ boost::asio::buffer(data, length),
+ boost::asio::transfer_all(),
+ ec);
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+ return (res);
+}
+
+void
+UnixDomainSocket::asyncSend(const void* data, const size_t length,
+ const Handler& handler) {
+ impl_->asyncSend(data, length, handler);
+}
+
+size_t
+UnixDomainSocket::receive(void* data, size_t length) {
+ boost::system::error_code ec;
+ size_t res = impl_->socket_.receive(boost::asio::buffer(data, length), 0, ec);
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+ return (res);
+}
+
+void
+UnixDomainSocket::asyncReceive(void* data, const size_t length,
+ const Handler& handler) {
+ impl_->asyncReceive(data, length, handler);
+}
+
+void
+UnixDomainSocket::shutdown() {
+ impl_->shutdown();
+}
+
+void
+UnixDomainSocket::cancel() {
+ impl_->cancel();
+}
+
+void
+UnixDomainSocket::close() {
+ impl_->close();
+}
+
+boost::asio::local::stream_protocol::socket&
+UnixDomainSocket::getASIOSocket() const {
+ return (impl_->socket_);
+}
+
+} // end of namespace asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/unix_domain_socket.h b/src/lib/asiolink/unix_domain_socket.h
new file mode 100644
index 0000000000..cd02f41e56
--- /dev/null
+++ b/src/lib/asiolink/unix_domain_socket.h
@@ -0,0 +1,137 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UNIX_DOMAIN_SOCKET_H
+#define UNIX_DOMAIN_SOCKET_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Exception thrown upon socket error.
+class UnixDomainSocketError : public Exception {
+public:
+ UnixDomainSocketError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class UnixDomainSocketImpl;
+
+/// @brief Represents unix domain socket implemented in terms
+/// of boost asio.
+class UnixDomainSocket : public IOSocket {
+public:
+
+ /// @brief Callback type used in call to @ref UnixDomainSocket::asyncConnect.
+ typedef std::function<void(const boost::system::error_code&)> ConnectHandler;
+
+ /// @brief Callback type used in calls to @ref UnixDomainSocket::asyncSend
+ /// and @ref UnixDomainSocket::asyncReceive.
+ typedef std::function<void(const boost::system::error_code&, size_t)> Handler;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to IOService to be used by this
+ /// class.
+ explicit UnixDomainSocket(IOService& io_service);
+
+ /// @brief Returns native socket representation.
+ virtual int getNative() const;
+
+ /// @brief Always returns 0.
+ virtual int getProtocol() const;
+
+ /// @brief Connects the socket to the specified endpoint.
+ ///
+ /// @param path Path to the unix socket to which we should connect.
+ ///
+ /// @throw UnixDomainSocketError if error occurs.
+ void connect(const std::string& path);
+
+ /// @brief Asynchronously connects the socket to the specified endpoint.
+ ///
+ /// Always returns immediately.
+ ///
+ /// @param path Path to the unix socket to which we should connect.
+ /// @param handler Callback to be invoked when connection is established or
+ /// a connection error is signalled.
+ void asyncConnect(const std::string& path, const ConnectHandler& handler);
+
+ /// @brief Writes specified amount of data to a socket.
+ ///
+ /// @param data Pointer to data to be written.
+ /// @param length Number of bytes to be written.
+ ///
+ /// @return Number of bytes written.
+ /// @throw UnixDomainSocketError if error occurs.
+ size_t write(const void* data, size_t length);
+
+ /// @brief Asynchronously sends data over the socket.
+ ///
+ /// Always returns immediately.
+ ///
+ /// @param data Pointer to data to be sent.
+ /// @param length Number of bytes to be sent.
+ /// @param handler Callback to be invoked when data have been sent or
+ /// sending error is signalled.
+ void asyncSend(const void* data, const size_t length, const Handler& handler);
+
+ /// @brief Receives data from a socket.
+ ///
+ /// @param [out] data Pointer to a location into which the read data should
+ /// be stored.
+ /// @param length Length of the buffer.
+ ///
+ /// @return Number of bytes read.
+ /// @throw UnixDomainSocketError if error occurs.
+ size_t receive(void* data, size_t length);
+
+ /// @brief Asynchronously receives data over the socket.
+ ///
+ /// Always returns immediately.
+ /// @param [out] data Pointer to a location into which the read data should
+ /// be stored.
+ /// @param length Length of the buffer.
+ /// @param handler Callback to be invoked when data have been received or an
+ /// error is signalled.
+ void asyncReceive(void* data, const size_t length, const Handler& handler);
+
+ /// @brief Disables read and write operations on the socket.
+ ///
+ /// @throw UnixDomainSocketError if an error occurs during shutdown.
+ void shutdown();
+
+ /// @brief Cancels scheduled asynchronous operations on the socket.
+ ///
+ /// @throw UnixDomainSocketError if an error occurs during cancel operation.
+ void cancel();
+
+ /// @brief Closes the socket.
+ ///
+ /// @throw UnixDomainSocketError if an error occurs during closure.
+ void close();
+
+ /// @brief Returns reference to the underlying ASIO socket.
+ ///
+ /// @return Reference to underlying ASIO socket.
+ virtual boost::asio::local::stream_protocol::socket& getASIOSocket() const;
+
+private:
+
+ /// @brief Pointer to the implementation of this class.
+ boost::shared_ptr<UnixDomainSocketImpl> impl_;
+
+};
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // UNIX_DOMAIN_SOCKET_H
diff --git a/src/lib/asiolink/unix_domain_socket_acceptor.h b/src/lib/asiolink/unix_domain_socket_acceptor.h
new file mode 100644
index 0000000000..8aa11cadca
--- /dev/null
+++ b/src/lib/asiolink/unix_domain_socket_acceptor.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UNIX_DOMAIN_SOCKET_ACCEPTOR_H
+#define UNIX_DOMAIN_SOCKET_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_acceptor.h>
+#include <asiolink/unix_domain_socket.h>
+#include <functional>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Implements acceptor service for @ref UnixDomainSocket.
+///
+/// This class is used to accept new incoming connections over unix domain
+/// sockets.
+class UnixDomainSocketAcceptor : public IOAcceptor<boost::asio::local::stream_protocol,
+ std::function<void(const boost::system::error_code&)> > {
+public:
+
+ /// @brief Callback type used in call to @ref UnixDomainSocketAcceptor::asyncAccept.
+ typedef std::function<void(const boost::system::error_code&)> AcceptHandler;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ explicit UnixDomainSocketAcceptor(IOService& io_service)
+ : IOAcceptor<boost::asio::local::stream_protocol,
+ std::function<void(const boost::system::error_code&)> >(io_service) {
+ }
+
+ /// @brief Returns the transport protocol of the socket.
+ ///
+ /// @return AF_LOCAL.
+ virtual int getProtocol() const final {
+ return (AF_LOCAL);
+ }
+
+ /// @brief Asynchronously accept new connection.
+ ///
+ /// This method accepts new connection into the specified socket. When the
+ /// new connection arrives or an error occurs the specified callback function
+ /// is invoked.
+ ///
+ /// @param socket Socket into which connection should be accepted.
+ /// @param callback Callback function to be invoked when the new connection
+ /// arrives.
+ /// @tparam SocketType
+ void asyncAccept(const UnixDomainSocket& socket, const AcceptHandler& callback) {
+ asyncAcceptInternal(socket, callback);
+ }
+};
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // UNIX_DOMAIN_SOCKET_ACCEPTOR_H
diff --git a/src/lib/asiolink/unix_domain_socket_endpoint.h b/src/lib/asiolink/unix_domain_socket_endpoint.h
new file mode 100644
index 0000000000..9378a8371e
--- /dev/null
+++ b/src/lib/asiolink/unix_domain_socket_endpoint.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UNIX_DOMAIN_SOCKET_ENDPOINT_H
+#define UNIX_DOMAIN_SOCKET_ENDPOINT_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <string>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Endpoint for @ref UnixDomainSocket.
+///
+/// This is a simple class encapsulating ASIO unix domain socket endpoint.
+/// It is used to represent endpoints taking part in communication via
+/// unix domain sockets.
+class UnixDomainSocketEndpoint {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param endpoint_path Path to the socket descriptor.
+ explicit UnixDomainSocketEndpoint(const std::string& endpoint_path)
+ : endpoint_(endpoint_path) {
+ }
+
+ /// @brief Returns underlying ASIO endpoint.
+ const boost::asio::local::stream_protocol::endpoint&
+ getASIOEndpoint() const {
+ return (endpoint_);
+ }
+
+private:
+
+ /// @brief Underlying ASIO endpoint.
+ boost::asio::local::stream_protocol::endpoint endpoint_;
+
+};
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // UNIX_DOMAIN_SOCKET_ENDPOINT_H
diff --git a/src/lib/cc/Makefile.am b/src/lib/cc/Makefile.am
index 8a734761f8..0cf1634773 100644
--- a/src/lib/cc/Makefile.am
+++ b/src/lib/cc/Makefile.am
@@ -6,16 +6,23 @@ AM_CXXFLAGS = $(KEA_CXXFLAGS)
lib_LTLIBRARIES = libkea-cc.la
libkea_cc_la_SOURCES = data.cc data.h
+libkea_cc_la_SOURCES += cfg_to_element.h dhcp_config_error.h
libkea_cc_la_SOURCES += command_interpreter.cc command_interpreter.h
+libkea_cc_la_SOURCES += json_feed.cc json_feed.h
+libkea_cc_la_SOURCES += simple_parser.cc simple_parser.h
-libkea_cc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_cc_la_LIBADD = $(top_builddir)/src/lib/util/libkea-util.la
+libkea_cc_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_cc_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
libkea_cc_la_LIBADD += $(BOOST_LIBS)
-libkea_cc_la_LDFLAGS = -no-undefined -version-info 1:0:0
+libkea_cc_la_LDFLAGS = -no-undefined -version-info 2:0:0
# Since data.h is now used in the hooks interface, it needs to be
# installed on target system.
libkea_cc_includedir = $(pkgincludedir)/cc
-libkea_cc_include_HEADERS = data.h
+libkea_cc_include_HEADERS = cfg_to_element.h data.h dhcp_config_error.h
+
+EXTRA_DIST = cc.dox
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/cc/cc.dox b/src/lib/cc/cc.dox
new file mode 100644
index 0000000000..4a34197681
--- /dev/null
+++ b/src/lib/cc/cc.dox
@@ -0,0 +1,99 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+ @page libcc libkea-cc - Kea Configuration Utilities Library
+
+@section ccSimpleParser Simple JSON Parser
+
+Since the early beginnings, our configuration parsing code was a mess. It
+started back in 2011 when Tomek joined ISC recently and was told to implement
+Kea configuration handling in similar way as DNS Auth module. The code grew
+over time (DHCP configuration is significantly more complex than DNS, with
+more interdependent values) and as of Kea 1.1 release it became very difficult
+to manage. The decision has been made to significantly refactor or even
+partially rewrite the parser code. The design for this effort is documented
+here: http://kea.isc.org/wiki/SimpleParser It discusses the original issues
+and the proposed architecture.
+
+There are several aspects of this new approach. The base class for all parsers
+is @ref isc::data::SimpleParser. It simplifies the parsers based on
+@ref isc::dhcp::DhcpConfigParser by rejecting the
+concept of build/commit phases. Instead, there should be a single method
+called parse that takes ConstElementPtr as a single parameter (that's the
+JSON structures to be parsed) and returns the config structure to be used
+in CfgMgr. An example of such a method can be the following:
+
+@code
+std::pair<OptionDescriptor, std::string>
+OptionDataParser::parse(isc::data::ConstElementPtr single_option)
+@endcode
+
+Since each derived class will have the same parameter, but a different return
+type, it's not possible to use virtual methods mechanism. That's perfectly
+ok, though, as there is only a single instance of the class needed to parse
+arbitrary number of parameters of the same type. There is no need to
+keep pointers to the parser object. As such there are fewer incentives to have
+one generic way to handle all parsers.
+
+@subsection ccSimpleParserDefaults Default values in Simple Parser
+
+Another simplification comes from the fact that almost all parameters
+are mandatory in SimpleParser. One source of complexities in the old
+parser was the necessity to deal with optional parameters. Simple
+parser deals with that by explicitly requiring the input structure to
+have all parameters filled. Obviously, it's not feasible to expect
+everyone to always specify all parameters, therefore there's an easy
+way to fill missing parameters with their default values. There are
+several methods to do this, but the most generic one is:
+
+@code
+static size_t
+isc::data::SimpleParser::setDefaults(isc::data::ElementPtr scope,
+ const SimpleDefaults& default_values);
+@endcode
+
+It takes a pointer to element to be filled with default values and
+vector of default values. Having those values specified in a single
+place in a way that can easily be read even by non-programmers is a
+big advantage of this approach. Here's an example from simple_parser.cc file:
+
+@code
+/// This table defines default values for option definitions in DHCPv6
+const SimpleDefaults OPTION6_DEF_DEFAULTS = {
+ { "record-types", Element::string, ""},
+ { "space", Element::string, "dhcp6"},
+ { "array", Element::boolean, "false"},
+ { "encapsulate", Element::string, "" }
+};
+@endcode
+
+This array (which technically is implemented as a vector and
+initialized the C++11 way) can be passed to the aforementioned
+setDefaults. That code will iterate over all default values and see if
+there are explicit values provided. If not, the gaps will be filled
+with default values. There are also convenience methods specified for
+filling in option data defaults, option definition defaults and
+setAllDefaults that sets all defaults (starts with global, but then
+walks down the Element tree and fills defaults in subsequent scopes).
+
+@subsection ccSimpleParserInherits Inheriting parameters between scopes
+
+SimpleParser provides a mechanism to inherit parameters between scopes,
+e.g. to inherit global parameters in the subnet scope if more specific
+values are not defined in the subnet scope. This is achieved by calling
+@code
+static size_t SimpleParser::deriveParams(isc::data::ConstElementPtr parent,
+ isc::data::ElementPtr child,
+ const ParamsList& params);
+
+@endcode
+
+ParamsList is a simple vector<string>. There will be more specific
+methods implemented in the future, but for the time being only
+@ref isc::data::SimpleParser::deriveParams is implemented.
+
+*/
diff --git a/src/lib/cc/cfg_to_element.h b/src/lib/cc/cfg_to_element.h
new file mode 100644
index 0000000000..480dd1fa74
--- /dev/null
+++ b/src/lib/cc/cfg_to_element.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef CFG_TO_ELEMENT_H
+#define CFG_TO_ELEMENT_H
+
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+
+namespace isc {
+
+/// @brief Cannot unparse error
+///
+/// This exception is expected to be thrown when toElement fails
+/// and to skip flawed elements is not wanted.
+class ToElementError : public isc::Exception {
+public:
+ ToElementError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+namespace data {
+
+/// @brief Abstract class for configuration Cfg_* classes
+///
+struct CfgToElement {
+ /// Destructor
+ virtual ~CfgToElement() { }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// Returns an element which must parse into the same object, i.e.
+ /// @code
+ /// for all valid config C parse(parse(C)->toElement()) == parse(C)
+ /// @endcode
+ ///
+ /// @return a pointer to a configuration which can be parsed into
+ /// the initial configuration object
+ virtual isc::data::ElementPtr toElement() const = 0;
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // CFG_TO_ELEMENT_H
diff --git a/src/lib/cc/command_interpreter.cc b/src/lib/cc/command_interpreter.cc
index be12190be4..bbdc3570fc 100644
--- a/src/lib/cc/command_interpreter.cc
+++ b/src/lib/cc/command_interpreter.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,8 +8,9 @@
#include <exceptions/exceptions.h>
#include <cc/command_interpreter.h>
-#include <string>
#include <cc/data.h>
+#include <string>
+#include <set>
using namespace std;
@@ -94,6 +95,43 @@ parseAnswer(int &rcode, const ConstElementPtr& msg) {
return (msg->get(CONTROL_TEXT));
}
+std::string
+answerToText(const ConstElementPtr& msg) {
+ if (!msg) {
+ isc_throw(CtrlChannelError, "No answer specified");
+ }
+ if (msg->getType() != Element::map) {
+ isc_throw(CtrlChannelError,
+ "Invalid answer Element specified, expected map");
+ }
+ if (!msg->contains(CONTROL_RESULT)) {
+ isc_throw(CtrlChannelError,
+ "Invalid answer specified, does not contain mandatory 'result'");
+ }
+
+ ConstElementPtr result = msg->get(CONTROL_RESULT);
+ if (result->getType() != Element::integer) {
+ isc_throw(CtrlChannelError,
+ "Result element in answer message is not a string");
+ }
+
+ stringstream txt;
+ int rcode = result->intValue();
+ if (rcode == 0) {
+ txt << "success(0)";
+ } else {
+ txt << "failure(" << rcode << ")";
+ }
+
+ // Was any text provided? If yes, include it.
+ ConstElementPtr txt_elem = msg->get(CONTROL_TEXT);
+ if (txt_elem) {
+ txt << ", text=" << txt_elem->stringValue();
+ }
+
+ return (txt.str());
+}
+
ConstElementPtr
createCommand(const std::string& command) {
return (createCommand(command, ElementPtr()));
@@ -134,5 +172,56 @@ parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
return (cmd->stringValue());
}
+ConstElementPtr
+combineCommandsLists(const ConstElementPtr& response1,
+ const ConstElementPtr& response2) {
+ // Usually when this method is called there should be two non-null
+ // responses. If there is just a single response, return this
+ // response.
+ if (!response1 && response2) {
+ return (response2);
+
+ } else if (response1 && !response2) {
+ return (response1);
+
+ } else if (!response1 && !response2) {
+ return (ConstElementPtr());
+
+ } else {
+ // Both responses are non-null so we need to combine the lists
+ // of supported commands if the status codes are 0.
+ int status_code;
+ ConstElementPtr args1 = parseAnswer(status_code, response1);
+ if (status_code != 0) {
+ return (response1);
+ }
+
+ ConstElementPtr args2 = parseAnswer(status_code, response2);
+ if (status_code != 0) {
+ return (response2);
+ }
+
+ const std::vector<ElementPtr> vec1 = args1->listValue();
+ const std::vector<ElementPtr> vec2 = args2->listValue();
+
+ // Storing command names in a set guarantees that the non-unique
+ // command names are aggregated.
+ std::set<std::string> combined_set;
+ for (auto v = vec1.cbegin(); v != vec1.cend(); ++v) {
+ combined_set.insert((*v)->stringValue());
+ }
+ for (auto v = vec2.cbegin(); v != vec2.cend(); ++v) {
+ combined_set.insert((*v)->stringValue());
+ }
+
+ // Create a combined list of commands.
+ ElementPtr combined_list = Element::createList();
+ for (auto s = combined_set.cbegin(); s != combined_set.cend(); ++s) {
+ combined_list->add(Element::create(*s));
+ }
+ return (createAnswer(CONTROL_RESULT_SUCCESS, combined_list));
+ }
+}
+
}
}
diff --git a/src/lib/cc/command_interpreter.h b/src/lib/cc/command_interpreter.h
index c42f2fd49f..eeaa500517 100644
--- a/src/lib/cc/command_interpreter.h
+++ b/src/lib/cc/command_interpreter.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -38,6 +38,14 @@ const int CONTROL_RESULT_SUCCESS = 0;
/// @brief Status code indicating a general failure
const int CONTROL_RESULT_ERROR = 1;
+/// @brief Status code indicating that the specified command is not supported.
+const int CONTROL_RESULT_COMMAND_UNSUPPORTED = 2;
+
+/// @brief Status code indicating that the specified command was completed
+/// correctly, but failed to produce any results. For example, get
+/// completed the search, but couldn't find the object it was looking for.
+const int CONTROL_RESULT_EMPTY = 3;
+
/// @brief A standard control channel exception that is thrown if a function
/// is there is a problem with one of the messages
class CtrlChannelError : public isc::Exception {
@@ -73,7 +81,7 @@ isc::data::ConstElementPtr createAnswer(const int status_code,
/// @brief Creates a standard config/command level answer message
///
/// @param status_code The return code (0 for success)
-/// @param status textual represenation of the status (used mostly for errors)
+/// @param status textual representation of the status (used mostly for errors)
/// @param arg The optional argument for the answer. This can be of
/// any Element type. May be NULL.
/// @return Standard command/config answer message
@@ -90,6 +98,12 @@ isc::data::ConstElementPtr createAnswer(const int status_code,
isc::data::ConstElementPtr parseAnswer(int &status_code,
const isc::data::ConstElementPtr& msg);
+/// @brief Converts answer to printable text
+///
+/// @param msg answer to be parsed
+/// @return printable string
+std::string answerToText(const isc::data::ConstElementPtr& msg);
+
/// @brief Creates a standard config/command command message with no
/// argument (of the form { "command": "my_command" })
///
@@ -120,6 +134,23 @@ isc::data::ConstElementPtr createCommand(const std::string& command,
std::string parseCommand(isc::data::ConstElementPtr& arg,
isc::data::ConstElementPtr command);
+/// @brief Combines lists of commands carried in two responses.
+///
+/// This method is used to combine list of commands returned by the
+/// two command managers.
+///
+/// If the same command appears in two responses only a single
+/// instance is returned in the combined response.
+///
+/// @param response1 First command response.
+/// @param response2 Second command response.
+///
+/// @return Pointer to the 'list-commands' response holding combined
+/// list of commands.
+isc::data::ConstElementPtr
+combineCommandsLists(const isc::data::ConstElementPtr& response1,
+ const isc::data::ConstElementPtr& response2);
+
}; // end of namespace isc::config
}; // end of namespace isc
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
index 65d9f6808f..4a83e688e0 100644
--- a/src/lib/cc/data.cc
+++ b/src/lib/cc/data.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,15 +11,16 @@
#include <cstring>
#include <cassert>
#include <climits>
+#include <list>
#include <map>
#include <cstdio>
#include <iostream>
+#include <iomanip>
#include <string>
#include <sstream>
#include <fstream>
#include <cerrno>
-#include <boost/algorithm/string.hpp> // for iequals
#include <boost/lexical_cast.hpp>
#include <cmath>
@@ -86,7 +87,7 @@ Element::getValue(std::string&) const {
}
bool
-Element::getValue(std::vector<ConstElementPtr>&) const {
+Element::getValue(std::vector<ElementPtr>&) const {
return (false);
}
@@ -116,7 +117,7 @@ Element::setValue(const std::string&) {
}
bool
-Element::setValue(const std::vector<ConstElementPtr>&) {
+Element::setValue(const std::vector<ElementPtr>&) {
return (false);
}
@@ -130,13 +131,18 @@ Element::get(const int) const {
throwTypeError("get(int) called on a non-list Element");
}
+ElementPtr
+Element::getNonConst(const int) const {
+ throwTypeError("get(int) called on a non-list Element");
+}
+
void
-Element::set(const size_t, ConstElementPtr) {
+Element::set(const size_t, ElementPtr) {
throwTypeError("set(int, element) called on a non-list Element");
}
void
-Element::add(ConstElementPtr) {
+Element::add(ElementPtr) {
throwTypeError("add() called on a non-list Element");
}
@@ -457,10 +463,10 @@ fromStringstreamBool(std::istream& in, const std::string& file,
// This will move the pos to the end of the value.
const std::string word = wordFromStringstream(in, pos);
- if (boost::iequals(word, "True")) {
+ if (word == "true") {
return (Element::create(true, Element::Position(file, line,
start_pos)));
- } else if (boost::iequals(word, "False")) {
+ } else if (word == "false") {
return (Element::create(false, Element::Position(file, line,
start_pos)));
} else {
@@ -479,7 +485,7 @@ fromStringstreamNull(std::istream& in, const std::string& file,
const uint32_t start_pos = pos;
// This will move the pos to the end of the value.
const std::string word = wordFromStringstream(in, pos);
- if (boost::iequals(word, "null")) {
+ if (word == "null") {
return (Element::create(Element::Position(file, line, start_pos)));
} else {
throwJSONError(std::string("Bad null value: ") + word, file,
@@ -507,7 +513,7 @@ fromStringstreamList(std::istream& in, const std::string& file, int& line,
{
int c = 0;
ElementPtr list = Element::createList(Element::Position(file, line, pos));
- ConstElementPtr cur_list_element;
+ ElementPtr cur_list_element;
skipChars(in, WHITESPACE, line, pos);
while (c != EOF && c != ']') {
@@ -658,16 +664,13 @@ Element::fromJSON(std::istream& in, const std::string& file, int& line,
el_read = true;
break;
case 't':
- case 'T':
case 'f':
- case 'F':
in.putback(c);
--pos;
element = fromStringstreamBool(in, file, line, pos);
el_read = true;
break;
case 'n':
- case 'N':
in.putback(c);
--pos;
element = fromStringstreamNull(in, file, line, pos);
@@ -795,7 +798,17 @@ StringElement::toJSON(std::ostream& ss) const {
ss << '\\' << 't';
break;
default:
- ss << c;
+ if ((c >= 0) && (c < 0x20)) {
+ std::ostringstream esc;
+ esc << "\\u"
+ << hex
+ << setw(4)
+ << setfill('0')
+ << (static_cast<unsigned>(c) & 0xff);
+ ss << esc.str();
+ } else {
+ ss << c;
+ }
}
}
ss << "\"";
@@ -805,8 +818,8 @@ void
ListElement::toJSON(std::ostream& ss) const {
ss << "[ ";
- const std::vector<ConstElementPtr>& v = listValue();
- for (std::vector<ConstElementPtr>::const_iterator it = v.begin();
+ const std::vector<ElementPtr>& v = listValue();
+ for (std::vector<ElementPtr>::const_iterator it = v.begin();
it != v.end(); ++it) {
if (it != v.begin()) {
ss << ", ";
@@ -1055,6 +1068,241 @@ merge(ElementPtr element, ConstElementPtr other) {
}
}
+ElementPtr
+copy(ConstElementPtr from, int level) {
+ if (isNull(from)) {
+ isc_throw(BadValue, "copy got a null pointer");
+ }
+ int from_type = from->getType();
+ if (from_type == Element::integer) {
+ return (ElementPtr(new IntElement(from->intValue())));
+ } else if (from_type == Element::real) {
+ return (ElementPtr(new DoubleElement(from->doubleValue())));
+ } else if (from_type == Element::boolean) {
+ return (ElementPtr(new BoolElement(from->boolValue())));
+ } else if (from_type == Element::null) {
+ return (ElementPtr(new NullElement()));
+ } else if (from_type == Element::string) {
+ return (ElementPtr(new StringElement(from->stringValue())));
+ } else if (from_type == Element::list) {
+ ElementPtr result = ElementPtr(new ListElement());
+ typedef std::vector<ElementPtr> ListType;
+ const ListType& value = from->listValue();
+ for (ListType::const_iterator it = value.cbegin();
+ it != value.cend(); ++it) {
+ if (level == 0) {
+ result->add(*it);
+ } else {
+ result->add(copy(*it, level - 1));
+ }
+ }
+ return (result);
+ } else if (from_type == Element::map) {
+ ElementPtr result = ElementPtr(new MapElement());
+ typedef std::map<std::string, ConstElementPtr> MapType;
+ const MapType& value = from->mapValue();
+ for (MapType::const_iterator it = value.cbegin();
+ it != value.cend(); ++it) {
+ if (level == 0) {
+ result->set(it->first, it->second);
+ } else {
+ result->set(it->first, copy(it->second, level - 1));
+ }
+ }
+ return (result);
+ } else {
+ isc_throw(BadValue, "copy got an element of type: " << from_type);
+ }
+}
+
+namespace {
+
+// Helper function which blocks infinite recursion
+bool
+isEquivalent0(ConstElementPtr a, ConstElementPtr b, unsigned level)
+{
+ // check looping forever on cycles
+ if (!level) {
+ isc_throw(BadValue, "isEquivalent got infinite recursion: "
+ "arguments include cycles");
+ }
+ if (!a || !b) {
+ isc_throw(BadValue, "isEquivalent got a null pointer");
+ }
+ // check types
+ if (a->getType() != b->getType()) {
+ return (false);
+ }
+ if (a->getType() == Element::list) {
+ // check empty
+ if (a->empty()) {
+ return (b->empty());
+ }
+ // check size
+ if (a->size() != b->size()) {
+ return (false);
+ }
+
+ // copy b into a list
+ const size_t s = a->size();
+ typedef std::list<ConstElementPtr> ListType;
+ ListType l;
+ for (size_t i = 0; i < s; ++i) {
+ l.push_back(b->get(i));
+ }
+
+ // iterate on a
+ for (size_t i = 0; i < s; ++i) {
+ ConstElementPtr item = a->get(i);
+ // lookup this item in the list
+ bool found = false;
+ for (ListType::iterator it = l.begin();
+ it != l.end(); ++it) {
+ // if found in the list remove it
+ if (isEquivalent0(item, *it, level - 1)) {
+ found = true;
+ l.erase(it);
+ break;
+ }
+ }
+ // if not found argument differs
+ if (!found) {
+ return (false);
+ }
+ }
+
+ // sanity check: the list must be empty
+ if (!l.empty()) {
+ isc_throw(Unexpected, "isEquivalent internal error");
+ }
+ return (true);
+ } else if (a->getType() == Element::map) {
+ // iterate on the first map
+ typedef std::map<std::string, ConstElementPtr> MapType;
+ const MapType& ma = a->mapValue();
+ for (MapType::const_iterator it = ma.begin();
+ it != ma.end() ; ++it) {
+ // get the b value for the given keyword and recurse
+ ConstElementPtr item = b->get(it->first);
+ if (!item || !isEquivalent0(it->second, item, level - 1)) {
+ return (false);
+ }
+ }
+ // iterate on the second map
+ const MapType& mb = b->mapValue();
+ for (MapType::const_iterator it = mb.begin();
+ it != mb.end() ; ++it) {
+ // check if the keyword exists
+ if (!a->contains(it->first)) {
+ return (false);
+ }
+ }
+ return (true);
+ } else {
+ return (a->equals(*b));
+ }
+}
+
+}
+
+bool
+isEquivalent(ConstElementPtr a, ConstElementPtr b) {
+ return (isEquivalent0(a, b, 100));
+}
+
+void
+prettyPrint(ConstElementPtr element, std::ostream& out,
+ unsigned indent, unsigned step) {
+ if (!element) {
+ isc_throw(BadValue, "prettyPrint got a null pointer");
+ }
+ if (element->getType() == Element::list) {
+ // empty list case
+ if (element->empty()) {
+ out << "[ ]";
+ return;
+ }
+
+ // complex ? multiline : oneline
+ if (!element->get(0)) {
+ isc_throw(BadValue, "prettyPrint got a null pointer");
+ }
+ int first_type = element->get(0)->getType();
+ bool complex = false;
+ if ((first_type == Element::list) || (first_type == Element::map)) {
+ complex = true;
+ }
+ std::string separator = complex ? ",\n" : ", ";
+
+ // open the list
+ out << "[" << (complex ? "\n" : " ");
+
+ // iterate on items
+ typedef std::vector<ElementPtr> ListType;
+ const ListType& l = element->listValue();
+ for (ListType::const_iterator it = l.begin();
+ it != l.end(); ++it) {
+ // add the separator if not the first item
+ if (it != l.begin()) {
+ out << separator;
+ }
+ // add indentation
+ if (complex) {
+ out << std::string(indent + step, ' ');
+ }
+ // recursive call
+ prettyPrint(*it, out, indent + step, step);
+ }
+
+ // close the list
+ if (complex) {
+ out << "\n" << std::string(indent, ' ');
+ } else {
+ out << " ";
+ }
+ out << "]";
+ } else if (element->getType() == Element::map) {
+ // empty map case
+ if (element->size() == 0) {
+ out << "{ }";
+ return;
+ }
+
+ // open the map
+ out << "{\n";
+
+ // iterate on keyword: value
+ typedef std::map<std::string, ConstElementPtr> MapType;
+ const MapType& m = element->mapValue();
+ for (MapType::const_iterator it = m.begin();
+ it != m.end(); ++it) {
+ // add the separator if not the first item
+ if (it != m.begin()) {
+ out << ",\n";
+ }
+ // add indentation
+ out << std::string(indent + step, ' ');
+ // add keyword:
+ out << "\"" << it->first << "\": ";
+ // recursive call
+ prettyPrint(it->second, out, indent + step, step);
+ }
+
+ // close the map
+ out << "\n" << std::string(indent, ' ') << "}";
+ } else {
+ // not a list or a map
+ element->toJSON(out);
+ }
+}
+
+std::string
+prettyPrint(ConstElementPtr element, unsigned indent, unsigned step) {
+ std::stringstream ss;
+ prettyPrint(element, ss, indent, step);
+ return (ss.str());
+}
+
void Element::preprocess(std::istream& in, std::stringstream& out) {
std::string line;
@@ -1066,7 +1314,7 @@ void Element::preprocess(std::istream& in, std::stringstream& out) {
line = "";
}
- // getline() removes end line charaters. Unfortunately, we need
+ // getline() removes end line characters. Unfortunately, we need
// it for getting the line numbers right (in case we report an
// error.
out << line;
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
index 791a555344..d609615e9b 100644
--- a/src/lib/cc/data.h
+++ b/src/lib/cc/data.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -23,7 +23,7 @@ typedef boost::shared_ptr<Element> ElementPtr;
typedef boost::shared_ptr<const Element> ConstElementPtr;
///
-/// \brief A standard Data module exception that is thrown if a function
+/// @brief A standard Data module exception that is thrown if a function
/// is called for an Element that has a wrong type (e.g. int_value on a
/// ListElement)
///
@@ -34,7 +34,7 @@ public:
};
///
-/// \brief A standard Data module exception that is thrown if a parse
+/// @brief A standard Data module exception that is thrown if a parse
/// error is encountered when constructing an Element from a string
///
// i'd like to use Exception here but we need one that is derived from
@@ -47,38 +47,38 @@ public:
};
///
-/// \brief The \c Element class represents a piece of data, used by
+/// @brief The @c Element class represents a piece of data, used by
/// the command channel and configuration parts.
///
-/// An \c Element can contain simple types (int, real, string, bool and
+/// An @c Element can contain simple types (int, real, string, bool and
/// None), and composite types (list and string->element maps)
///
/// Elements should in calling functions usually be referenced through
-/// an \c ElementPtr, which can be created using the factory functions
-/// \c Element::create() and \c Element::fromJSON()
+/// an @c ElementPtr, which can be created using the factory functions
+/// @c Element::create() and @c Element::fromJSON()
///
/// Notes to developers: Element is a base class, implemented by a
/// specific subclass for each type (IntElement, BoolElement, etc).
/// Element does define all functions for all types, and defaults to
-/// raising a \c TypeError for functions that are not supported for
+/// raising a @c TypeError for functions that are not supported for
/// the type in question.
///
class Element {
public:
- /// \brief Represents the position of the data element within a
+ /// @brief Represents the position of the data element within a
/// configuration string.
///
/// Position comprises a file name, line number and an offset within this
/// line where the element value starts. For example, if the JSON string is
///
- /// \code
+ /// @code
/// { "foo": "some string",
/// "bar": 123 }
/// \endcode
///
/// the position of the element "bar" is: line_ = 2; pos_ = 9, because
- /// begining of the value "123" is at offset 9 from the beginning of
+ /// beginning of the value "123" is at offset 9 from the beginning of
/// the second line, including whitespaces.
///
/// Note that the @c Position structure is used as an argument to @c Element
@@ -90,27 +90,27 @@ public:
uint32_t line_; ///< Line number.
uint32_t pos_; ///< Position within the line.
- /// \brief Default constructor.
+ /// @brief Default constructor.
Position() : file_(""), line_(0), pos_(0) {
}
- /// \brief Constructor.
+ /// @brief Constructor.
///
- /// \param file File name.
- /// \param line Line number.
- /// \param pos Position within the line.
+ /// @param file File name.
+ /// @param line Line number.
+ /// @param pos Position within the line.
Position(const std::string& file, const uint32_t line,
const uint32_t pos)
: file_(file), line_(line), pos_(pos) {
}
- /// \brief Returns the position in the textual format.
+ /// @brief Returns the position in the textual format.
///
/// The returned position has the following format: file:line:pos.
std::string str() const;
};
- /// \brief Returns @c Position object with line_ and pos_ set to 0, and
+ /// @brief Returns @c Position object with line_ and pos_ set to 0, and
/// with an empty file name.
///
/// The object containing two zeros is a default for most of the
@@ -128,15 +128,15 @@ private:
// function getType?
int type_;
- /// \brief Position of the element in the configuration string.
+ /// @brief Position of the element in the configuration string.
Position position_;
protected:
- /// \brief Constructor.
+ /// @brief Constructor.
///
- /// \param t Element type.
- /// \param pos Structure holding position of the value of the data element.
+ /// @param t Element type.
+ /// @param pos Structure holding position of the value of the data element.
/// It comprises the line number and the position within this line. The values
/// held in this structure are used for error logging purposes.
Element(int t, const Position& pos = ZERO_POSITION())
@@ -152,10 +152,10 @@ public:
// base class; make dtor virtual
virtual ~Element() {};
- /// \return the type of this element
+ /// @return the type of this element
int getType() const { return (type_); }
- /// \brief Returns position where the data element's value starts in a
+ /// @brief Returns position where the data element's value starts in a
/// configuration string.
///
/// @warning The returned reference is valid as long as the object which
@@ -168,17 +168,17 @@ public:
///
/// The resulting string will contain the Element in JSON format.
///
- /// \return std::string containing the string representation
+ /// @return std::string containing the string representation
std::string str() const;
/// Returns the wireformat for the Element and all its child
/// elements.
///
- /// \return std::string containing the element in wire format
+ /// @return std::string containing the element in wire format
std::string toWire() const;
void toWire(std::ostream& out) const;
- /// \brief Add the position to a TypeError message
+ /// @brief Add the position to a TypeError message
/// should be used in place of isc_throw(TypeError, error)
#define throwTypeError(error) \
{ \
@@ -186,23 +186,23 @@ public:
if ((position_.file_ != "") || \
(position_.line_ != 0) || \
(position_.pos_ != 0)) { \
- msg_ += " in " + position_.str(); \
+ msg_ += " in (" + position_.str() + ")"; \
} \
isc_throw(TypeError, msg_); \
}
- /// \name pure virtuals, every derived class must implement these
+ /// @name pure virtuals, every derived class must implement these
- /// \return true if the other ElementPtr has the same type and value
+ /// @return true if the other ElementPtr has the same type and value
virtual bool equals(const Element& other) const = 0;
/// Converts the Element to JSON format and appends it to
/// the given stringstream.
virtual void toJSON(std::ostream& ss) const = 0;
- /// \name Type-specific getters
+ /// @name Type-specific getters
///
- /// \brief These functions only
+ /// @brief These functions only
/// work on their corresponding Element type. For all other
/// types, a TypeError is thrown.
/// If you want an exception-safe getter method, use
@@ -216,7 +216,7 @@ public:
{ throwTypeError("boolValue() called on non-Bool Element"); };
virtual std::string stringValue() const
{ throwTypeError("stringValue() called on non-string Element"); };
- virtual const std::vector<ConstElementPtr>& listValue() const {
+ virtual const std::vector<ElementPtr>& listValue() const {
// replace with real exception or empty vector?
throwTypeError("listValue() called on non-list Element");
};
@@ -226,9 +226,9 @@ public:
};
//@}
- /// \name Exception-safe getters
+ /// @name Exception-safe getters
///
- /// \brief The getValue() functions return false if the given reference
+ /// @brief The getValue() functions return false if the given reference
/// is of another type than the element contains
/// By default it always returns false; the derived classes
/// override the function for their type, copying their
@@ -239,14 +239,14 @@ public:
virtual bool getValue(double& t) const;
virtual bool getValue(bool& t) const;
virtual bool getValue(std::string& t) const;
- virtual bool getValue(std::vector<ConstElementPtr>& t) const;
+ virtual bool getValue(std::vector<ElementPtr>& t) const;
virtual bool getValue(std::map<std::string, ConstElementPtr>& t) const;
//@}
///
- /// \name Exception-safe setters.
+ /// @name Exception-safe setters.
///
- /// \brief Return false if the Element is not
+ /// @brief Return false if the Element is not
/// the right type. Set the value and return true if the Elements
/// is of the correct type
///
@@ -259,7 +259,7 @@ public:
virtual bool setValue(const double v);
virtual bool setValue(const bool t);
virtual bool setValue(const std::string& v);
- virtual bool setValue(const std::vector<ConstElementPtr>& v);
+ virtual bool setValue(const std::vector<ElementPtr>& v);
virtual bool setValue(const std::map<std::string, ConstElementPtr>& v);
//@}
@@ -267,29 +267,35 @@ public:
// Other functions for specific subtypes
- /// \name ListElement functions
+ /// @name ListElement functions
///
- /// \brief If the Element on which these functions are called are not
+ /// @brief If the Element on which these functions are called are not
/// an instance of ListElement, a TypeError exception is thrown.
//@{
/// Returns the ElementPtr at the given index. If the index is out
/// of bounds, this function throws an std::out_of_range exception.
- /// \param i The position of the ElementPtr to return
+ /// @param i The position of the ElementPtr to return
virtual ConstElementPtr get(const int i) const;
+ /// @brief returns element as non-const pointer
+ ///
+ /// @param i The position of the ElementPtr to retrieve
+ /// @return specified element pointer
+ virtual ElementPtr getNonConst(const int i) const;
+
/// Sets the ElementPtr at the given index. If the index is out
/// of bounds, this function throws an std::out_of_range exception.
- /// \param i The position of the ElementPtr to set
- /// \param element The ElementPtr to set at the position
- virtual void set(const size_t i, ConstElementPtr element);
+ /// @param i The position of the ElementPtr to set
+ /// @param element The ElementPtr to set at the position
+ virtual void set(const size_t i, ElementPtr element);
/// Adds an ElementPtr to the list
- /// \param element The ElementPtr to add
- virtual void add(ConstElementPtr element);
+ /// @param element The ElementPtr to add
+ virtual void add(ElementPtr element);
/// Removes the element at the given position. If the index is out
/// of nothing happens.
- /// \param i The index of the element to remove.
+ /// @param i The index of the element to remove.
virtual void remove(const int i);
/// Returns the number of elements in the list.
@@ -300,28 +306,28 @@ public:
//@}
- /// \name MapElement functions
+ /// @name MapElement functions
///
- /// \brief If the Element on which these functions are called are not
+ /// @brief If the Element on which these functions are called are not
/// an instance of MapElement, a TypeError exception is thrown.
//@{
/// Returns the ElementPtr at the given key
- /// \param name The key of the Element to return
- /// \return The ElementPtr at the given key, or null if not present
+ /// @param name The key of the Element to return
+ /// @return The ElementPtr at the given key, or null if not present
virtual ConstElementPtr get(const std::string& name) const;
/// Sets the ElementPtr at the given key
- /// \param name The key of the Element to set
- /// \param element The ElementPtr to set at the given key.
+ /// @param name The key of the Element to set
+ /// @param element The ElementPtr to set at the given key.
virtual void set(const std::string& name, ConstElementPtr element);
/// Remove the ElementPtr at the given key
- /// \param name The key of the Element to remove
+ /// @param name The key of the Element to remove
virtual void remove(const std::string& name);
/// Checks if there is data at the given key
- /// \param name The key of the Element to remove
- /// \return true if there is data at the key, false if not.
+ /// @param name The key of the Element checked for existence
+ /// @return true if there is data at the key, false if not.
virtual bool contains(const std::string& name) const;
/// Recursively finds any data at the given identifier. The
@@ -333,30 +339,30 @@ public:
/// Another Element at key "bar", the identifier for that last
/// element from the first is "foo/bar".
///
- /// \param identifier The identifier of the element to find
- /// \return The ElementPtr at the given identifier. Returns a
+ /// @param identifier The identifier of the element to find
+ /// @return The ElementPtr at the given identifier. Returns a
/// null ElementPtr if it is not found, which can be checked with
/// Element::is_null(ElementPtr e).
virtual ConstElementPtr find(const std::string& identifier) const;
- /// See \c Element::find()
- /// \param identifier The identifier of the element to find
- /// \param t Reference to store the resulting ElementPtr, if found.
- /// \return true if the element was found, false if not.
+ /// See @c Element::find()
+ /// @param identifier The identifier of the element to find
+ /// @param t Reference to store the resulting ElementPtr, if found.
+ /// @return true if the element was found, false if not.
virtual bool find(const std::string& identifier, ConstElementPtr& t) const;
//@}
- /// \name Factory functions
+ /// @name Factory functions
// TODO: should we move all factory functions to a different class
// so as not to burden the Element base with too many functions?
// and/or perhaps even to a separate header?
- /// \name Direct factory functions
- /// \brief These functions simply wrap the given data directly
+ /// @name Direct factory functions
+ /// @brief These functions simply wrap the given data directly
/// in an Element object, and return a reference to it, in the form
- /// of an \c ElementPtr.
+ /// of an @c ElementPtr.
/// These factory functions are exception-free (unless there is
/// no memory available, in which case bad_alloc is raised by the
/// underlying system).
@@ -384,42 +390,42 @@ public:
static ElementPtr create(const char *s,
const Position& pos = ZERO_POSITION());
- /// \brief Creates an empty ListElement type ElementPtr.
+ /// @brief Creates an empty ListElement type ElementPtr.
///
- /// \param pos A structure holding position of the data element value
+ /// @param pos A structure holding position of the data element value
/// in the configuration string. It is used for error logging purposes.
static ElementPtr createList(const Position& pos = ZERO_POSITION());
- /// \brief Creates an empty MapElement type ElementPtr.
+ /// @brief Creates an empty MapElement type ElementPtr.
///
- /// \param pos A structure holding position of the data element value
+ /// @param pos A structure holding position of the data element value
/// in the configuration string. It is used for error logging purposes.
static ElementPtr createMap(const Position& pos = ZERO_POSITION());
//@}
- /// \name Compound factory functions
+ /// @name Compound factory functions
- /// \brief These functions will parse the given string (JSON)
+ /// @brief These functions will parse the given string (JSON)
/// representation of a compound element. If there is a parse
/// error, an exception of the type isc::data::JSONError is thrown.
//@{
/// Creates an Element from the given JSON string
- /// \param in The string to parse the element from
- /// \param preproc specified whether preprocessing (e.g. comment removal)
+ /// @param in The string to parse the element from
+ /// @param preproc specified whether preprocessing (e.g. comment removal)
/// should be performed
- /// \return An ElementPtr that contains the element(s) specified
+ /// @return An ElementPtr that contains the element(s) specified
/// in the given string.
static ElementPtr fromJSON(const std::string& in, bool preproc = false);
/// Creates an Element from the given input stream containing JSON
/// formatted data.
///
- /// \param in The string to parse the element from
- /// \param preproc specified whether preprocessing (e.g. comment removal)
+ /// @param in The string to parse the element from
+ /// @param preproc specified whether preprocessing (e.g. comment removal)
/// should be performed
- /// \return An ElementPtr that contains the element(s) specified
+ /// @return An ElementPtr that contains the element(s) specified
/// in the given input stream.
static ElementPtr fromJSON(std::istream& in, bool preproc = false)
throw(JSONError);
@@ -427,11 +433,11 @@ public:
/// Creates an Element from the given input stream containing JSON
/// formatted data.
///
- /// \param in The string to parse the element from
- /// \param file_name specified input file name (used in error reporting)
- /// \param preproc specified whether preprocessing (e.g. comment removal)
+ /// @param in The string to parse the element from
+ /// @param file_name specified input file name (used in error reporting)
+ /// @param preproc specified whether preprocessing (e.g. comment removal)
/// should be performed
- /// \return An ElementPtr that contains the element(s) specified
+ /// @return An ElementPtr that contains the element(s) specified
/// in the given input stream.
static ElementPtr fromJSON(std::istream& in, const std::string& file_name,
bool preproc = false)
@@ -440,13 +446,13 @@ public:
/// Creates an Element from the given input stream, where we keep
/// track of the location in the stream for error reporting.
///
- /// \param in The string to parse the element from.
- /// \param file The input file name.
- /// \param line A reference to the int where the function keeps
+ /// @param in The string to parse the element from.
+ /// @param file The input file name.
+ /// @param line A reference to the int where the function keeps
/// track of the current line.
- /// \param pos A reference to the int where the function keeps
+ /// @param pos A reference to the int where the function keeps
/// track of the current position within the current line.
- /// \return An ElementPtr that contains the element(s) specified
+ /// @return An ElementPtr that contains the element(s) specified
/// in the given input stream.
// make this one private?
static ElementPtr fromJSON(std::istream& in, const std::string& file,
@@ -464,23 +470,23 @@ public:
bool preproc = false);
//@}
- /// \name Type name conversion functions
+ /// @name Type name conversion functions
/// Returns the name of the given type as a string
///
- /// \param type The type to return the name of
- /// \return The name of the type, or "unknown" if the type
+ /// @param type The type to return the name of
+ /// @return The name of the type, or "unknown" if the type
/// is not known.
static std::string typeToName(Element::types type);
/// Converts the string to the corresponding type
/// Throws a TypeError if the name is unknown.
///
- /// \param type_name The name to get the type of
- /// \return the corresponding type value
+ /// @param type_name The name to get the type of
+ /// @return the corresponding type value
static Element::types nameToType(const std::string& type_name);
- /// \brief input text preprocessor
+ /// @brief input text preprocessor
///
/// This method performs preprocessing of the input stream (which is
/// expected to contain a text version of to be parsed JSON). For now the
@@ -496,7 +502,7 @@ public:
/// @param out output stream (filtered content will be written here)
static void preprocess(std::istream& in, std::stringstream& out);
- /// \name Wire format factory functions
+ /// @name Wire format factory functions
/// These function pparse the wireformat at the given stringstream
/// (of the given length). If there is a parse error an exception
@@ -505,20 +511,20 @@ public:
//@{
/// Creates an Element from the wire format in the given
/// stringstream of the given length.
- /// Since the wire format is JSON, thise is the same as
+ /// Since the wire format is JSON, this is the same as
/// fromJSON, and could be removed.
///
- /// \param in The input stringstream.
- /// \param length The length of the wireformat data in the stream
- /// \return ElementPtr with the data that is parsed.
+ /// @param in The input stringstream.
+ /// @param length The length of the wireformat data in the stream
+ /// @return ElementPtr with the data that is parsed.
static ElementPtr fromWire(std::stringstream& in, int length);
/// Creates an Element from the wire format in the given string
- /// Since the wire format is JSON, thise is the same as
+ /// Since the wire format is JSON, this is the same as
/// fromJSON, and could be removed.
///
- /// \param s The input string
- /// \return ElementPtr with the data that is parsed.
+ /// @param s The input string
+ /// @return ElementPtr with the data that is parsed.
static ElementPtr fromWire(const std::string& s);
//@}
};
@@ -603,29 +609,30 @@ public:
};
class ListElement : public Element {
- std::vector<ConstElementPtr> l;
+ std::vector<ElementPtr> l;
public:
ListElement(const Position& pos = ZERO_POSITION())
: Element(list, pos) {}
- const std::vector<ConstElementPtr>& listValue() const { return (l); }
+ const std::vector<ElementPtr>& listValue() const { return (l); }
using Element::getValue;
- bool getValue(std::vector<ConstElementPtr>& t) const {
+ bool getValue(std::vector<ElementPtr>& t) const {
t = l;
return (true);
}
using Element::setValue;
- bool setValue(const std::vector<ConstElementPtr>& v) {
+ bool setValue(const std::vector<ElementPtr>& v) {
l = v;
return (true);
}
using Element::get;
ConstElementPtr get(int i) const { return (l.at(i)); }
+ ElementPtr getNonConst(int i) const { return (l.at(i)); }
using Element::set;
- void set(size_t i, ConstElementPtr e) {
+ void set(size_t i, ElementPtr e) {
l.at(i) = e;
}
- void add(ConstElementPtr e) { l.push_back(e); };
+ void add(ElementPtr e) { l.push_back(e); };
using Element::remove;
void remove(int i) { l.erase(l.begin() + i); };
void toJSON(std::ostream& ss) const;
@@ -691,12 +698,12 @@ public:
};
/// Checks whether the given ElementPtr is a NULL pointer
-/// \param p The ElementPtr to check
-/// \return true if it is NULL, false if not.
+/// @param p The ElementPtr to check
+/// @return true if it is NULL, false if not.
bool isNull(ConstElementPtr p);
///
-/// \brief Remove all values from the first ElementPtr that are
+/// @brief Remove all values from the first ElementPtr that are
/// equal in the second. Both ElementPtrs MUST be MapElements
/// The use for this function is to end up with a MapElement that
/// only contains new and changed values (for ModuleCCSession and
@@ -704,14 +711,14 @@ bool isNull(ConstElementPtr p);
/// Raises a TypeError if a or b are not MapElements
void removeIdentical(ElementPtr a, ConstElementPtr b);
-/// \brief Create a new ElementPtr from the first ElementPtr, removing all
+/// @brief Create a new ElementPtr from the first ElementPtr, removing all
/// values that are equal in the second. Both ElementPtrs MUST be MapElements.
/// The returned ElementPtr will be a MapElement that only contains new and
/// changed values (for ModuleCCSession and configuration update handlers).
/// Raises a TypeError if a or b are not MapElements
ConstElementPtr removeIdentical(ConstElementPtr a, ConstElementPtr b);
-/// \brief Merges the data from other into element.
+/// @brief Merges the data from other into element.
/// (on the first level). Both elements must be
/// MapElements.
/// Every string,value pair in other is copied into element
@@ -725,34 +732,78 @@ ConstElementPtr removeIdentical(ConstElementPtr a, ConstElementPtr b);
/// Raises a TypeError if either ElementPtr is not a MapElement
void merge(ElementPtr element, ConstElementPtr other);
+/// @brief Copy the data up to a nesting level.
+///
+/// The copy is a deep copy so nothing is shared if it is not
+/// under the given nesting level.
+///
+/// @param from the pointer to the element to copy
+/// @param level nesting level (default is 100, 0 means shallow copy,
+/// negative means outbound and perhaps looping forever).
+/// @return a pointer to a fresh copy
+/// \throw raises a BadValue is a null pointer occurs.
+ElementPtr copy(ConstElementPtr from, int level = 100);
+
+/// @brief Compares the data with other using unordered lists
+///
+/// This comparison function handles lists (JSON arrays) as
+/// unordered multi sets (multi means an item can occurs more
+/// than once as soon as it occurs the same number of times).
+bool isEquivalent(ConstElementPtr a, ConstElementPtr b);
+
+/// @brief Pretty prints the data into stream.
+///
+/// This operator converts the @c ConstElementPtr into a string and
+/// inserts it into the output stream @c out with an initial
+/// indentation @c indent and add at each level @c step spaces.
+///
+/// @param element A @c ConstElementPtr to pretty print
+/// @param out A @c std::ostream on which the print operation is performed
+/// @param indent An initial number of spaces to add each new line
+/// @param step A number of spaces to add to indentation at a new level
+void prettyPrint(ConstElementPtr element, std::ostream& out,
+ unsigned indent = 0, unsigned step = 2);
+
+/// @brief Pretty prints the data into string
+///
+/// This operator converts the @c ConstElementPtr into a string with
+/// an initial indentation @c indent and add at each level @c step spaces.
+///
+/// @param element A @c ConstElementPtr to pretty print
+/// @param indent An initial number of spaces to add each new line
+/// @param step A number of spaces to add to indentation at a new level
+/// @return a string where element was pretty printed
+std::string prettyPrint(ConstElementPtr element,
+ unsigned indent = 0, unsigned step = 2);
+
///
-/// \brief Insert Element::Position as a string into stream.
+/// @brief Insert Element::Position as a string into stream.
///
-/// This operator converts the \c Element::Position into a string and
-/// inserts it into the output stream \c out.
+/// This operator converts the @c Element::Position into a string and
+/// inserts it into the output stream @c out.
///
-/// \param out A \c std::ostream object on which the insertion operation is
+/// @param out A @c std::ostream object on which the insertion operation is
/// performed.
-/// \param pos The \c Element::Position structure to insert.
-/// \return A reference to the same \c std::ostream object referenced by
-/// parameter \c out after the insertion operation.
+/// @param pos The @c Element::Position structure to insert.
+/// @return A reference to the same @c std::ostream object referenced by
+/// parameter @c out after the insertion operation.
std::ostream& operator<<(std::ostream& out, const Element::Position& pos);
///
-/// \brief Insert the Element as a string into stream.
+/// @brief Insert the Element as a string into stream.
///
-/// This method converts the \c ElementPtr into a string with
-/// \c Element::str() and inserts it into the
-/// output stream \c out.
+/// This method converts the @c ElementPtr into a string with
+/// @c Element::str() and inserts it into the
+/// output stream @c out.
///
/// This function overloads the global operator<< to behave as described in
-/// ostream::operator<< but applied to \c ElementPtr objects.
+/// ostream::operator<< but applied to @c ElementPtr objects.
///
-/// \param out A \c std::ostream object on which the insertion operation is
+/// @param out A @c std::ostream object on which the insertion operation is
/// performed.
-/// \param e The \c ElementPtr object to insert.
-/// \return A reference to the same \c std::ostream object referenced by
-/// parameter \c out after the insertion operation.
+/// @param e The @c ElementPtr object to insert.
+/// @return A reference to the same @c std::ostream object referenced by
+/// parameter @c out after the insertion operation.
std::ostream& operator<<(std::ostream& out, const Element& e);
bool operator==(const Element& a, const Element& b);
diff --git a/src/lib/cc/dhcp_config_error.h b/src/lib/cc/dhcp_config_error.h
new file mode 100644
index 0000000000..eabc98de04
--- /dev/null
+++ b/src/lib/cc/dhcp_config_error.h
@@ -0,0 +1,73 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef DHCP_CONFIG_ERROR_H
+#define DHCP_CONFIG_ERROR_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+
+/// @brief Evaluation error exception raised when trying to parse.
+///
+/// This exception is expected to be thrown when parsing of the input
+/// configuration has failed. This exception is used by parsers.
+class ParseError : public isc::Exception {
+ public:
+ ParseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// An exception that is thrown if an error occurs while configuring
+/// any server.
+/// By convention when this exception is thrown there is a position
+/// between parentheses so the code style should be like this:
+///
+/// try {
+/// ...
+/// } catch (const ConfigError&) {
+/// throw;
+/// } catch (const std::exception& ex) {
+/// isc_throw(ConfigError, "message" << ex.what()
+/// << " (" << getPosition(what) << ")");
+/// }
+
+/// @todo: move this header into simple_parser.h as soon as
+/// there is no dependency through DhcpConfigParser
+/// @todo: create an isc_throw like macro to add the
+/// position more easily.
+/// @todo: replace all references to DhcpConfigError with ConfigError,
+/// then remove DhcpConfigError.
+class ConfigError : public isc::Exception {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ ConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+namespace dhcp {
+
+/// @brief To be removed. Please use ConfigError instead.
+class DhcpConfigError : public isc::Exception {
+public:
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ DhcpConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP_CONFIG_ERROR_H
diff --git a/src/lib/cc/json_feed.cc b/src/lib/cc/json_feed.cc
new file mode 100644
index 0000000000..40a0e4420c
--- /dev/null
+++ b/src/lib/cc/json_feed.cc
@@ -0,0 +1,312 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <cc/data.h>
+#include <cc/json_feed.h>
+#include <boost/bind.hpp>
+
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace config {
+
+const int JSONFeed::RECEIVE_START_ST;
+const int JSONFeed::WHITESPACE_BEFORE_JSON_ST;
+const int JSONFeed::JSON_START_ST;
+const int JSONFeed::INNER_JSON_ST;
+const int JSONFeed::JSON_END_ST;
+const int JSONFeed::FEED_OK_ST;
+const int JSONFeed::FEED_FAILED_ST;
+
+const int JSONFeed::DATA_READ_OK_EVT;
+const int JSONFeed::NEED_MORE_DATA_EVT;
+const int JSONFeed::MORE_DATA_PROVIDED_EVT;
+const int JSONFeed::FEED_OK_EVT;
+const int JSONFeed::FEED_FAILED_EVT;
+
+JSONFeed::JSONFeed()
+ : StateModel(), buffer_(), error_message_(), open_scopes_(0),
+ output_() {
+}
+
+void
+JSONFeed::initModel() {
+ // Initialize dictionaries of events and states.
+ initDictionaries();
+
+ // Set the current state to starting state and enter the run loop.
+ setState(RECEIVE_START_ST);
+
+ // Parsing starts from here.
+ postNextEvent(START_EVT);
+}
+
+void
+JSONFeed::poll() {
+ try {
+ // Process the input data until no more data is available or until
+ // JSON feed ends with matching closing brace.
+ do {
+ getState(getCurrState())->run();
+
+ } while (!isModelDone() && (getNextEvent() != NOP_EVT) &&
+ (getNextEvent() != NEED_MORE_DATA_EVT));
+ } catch (const std::exception& ex) {
+ abortModel(ex.what());
+ }
+}
+
+bool
+JSONFeed::needData() const {
+ return ((getNextEvent() == NEED_MORE_DATA_EVT) ||
+ (getNextEvent() == START_EVT));
+}
+
+bool
+JSONFeed::feedOk() const {
+ return ((getNextEvent() == END_EVT) &&
+ (getLastEvent() == FEED_OK_EVT));
+}
+
+ElementPtr
+JSONFeed::toElement() const {
+ if (needData()) {
+ isc_throw(JSONFeedError, "unable to retrieve the data form the"
+ " JSON feed while parsing hasn't finished");
+ }
+ try {
+ return (Element::fromWire(output_));
+
+ } catch (const std::exception& ex) {
+ isc_throw(JSONFeedError, ex.what());
+ }
+}
+
+void
+JSONFeed::postBuffer(const void* buf, const size_t buf_size) {
+ if (buf_size > 0) {
+ // The next event is NEED_MORE_DATA_EVT when the parser wants to
+ // signal that more data is needed. This method is called to supply
+ // more data and thus it should change the next event to
+ // MORE_DATA_PROVIDED_EVT.
+ if (getNextEvent() == NEED_MORE_DATA_EVT) {
+ transition(getCurrState(), MORE_DATA_PROVIDED_EVT);
+ }
+ buffer_.insert(buffer_.end(), static_cast<const char*>(buf),
+ static_cast<const char*>(buf) + buf_size);
+ }
+}
+
+void
+JSONFeed::defineEvents() {
+ StateModel::defineEvents();
+
+ // Define JSONFeed specific events.
+ defineEvent(DATA_READ_OK_EVT, "DATA_READ_OK_EVT");
+ defineEvent(NEED_MORE_DATA_EVT, "NEED_MORE_DATA_EVT");
+ defineEvent(MORE_DATA_PROVIDED_EVT, "MORE_DATA_PROVIDED_EVT");
+ defineEvent(FEED_OK_EVT, "FEED_OK_EVT");
+ defineEvent(FEED_FAILED_EVT, "FEED_FAILED_EVT");
+}
+
+void
+JSONFeed::verifyEvents() {
+ StateModel::verifyEvents();
+
+ getEvent(DATA_READ_OK_EVT);
+ getEvent(NEED_MORE_DATA_EVT);
+ getEvent(MORE_DATA_PROVIDED_EVT);
+ getEvent(FEED_OK_EVT);
+ getEvent(FEED_FAILED_EVT);
+}
+
+void
+JSONFeed::defineStates() {
+ // Call parent class implementation first.
+ StateModel::defineStates();
+
+ defineState(RECEIVE_START_ST, "RECEIVE_START_ST",
+ boost::bind(&JSONFeed::receiveStartHandler, this));
+ defineState(WHITESPACE_BEFORE_JSON_ST, "WHITESPACE_BEFORE_JSON_ST",
+ boost::bind(&JSONFeed::whiteSpaceBeforeJSONHandler, this));
+ defineState(INNER_JSON_ST, "INNER_JSON_ST",
+ boost::bind(&JSONFeed::innerJSONHandler, this));
+ defineState(JSON_END_ST, "JSON_END_ST",
+ boost::bind(&JSONFeed::endJSONHandler, this));
+}
+
+void
+JSONFeed::feedFailure(const std::string& error_msg) {
+ error_message_ = error_msg;
+ transition(FEED_FAILED_ST, FEED_FAILED_EVT);
+}
+
+void
+JSONFeed::onModelFailure(const std::string& explanation) {
+ if (error_message_.empty()) {
+ error_message_ = explanation;
+ }
+}
+
+bool
+JSONFeed::popNextFromBuffer(char& next) {
+ // If there are any characters in the buffer, pop next.
+ if (!buffer_.empty()) {
+ next = buffer_.front();
+ buffer_.pop_front();
+ return (true);
+ }
+ return (false);
+}
+
+char
+JSONFeed::getNextFromBuffer() {
+ unsigned int ev = getNextEvent();
+ char c = '\0';
+ // The caller should always provide additional data when the
+ // NEED_MORE_DATA_EVT occurs. If the next event is still
+ // NEED_MORE_DATA_EVT it indicates that the caller hasn't provided
+ // the data.
+ if (ev == NEED_MORE_DATA_EVT) {
+ isc_throw(JSONFeedError,
+ "JSONFeed requires new data to progress, but no data"
+ " have been provided. The transaction is aborted to avoid"
+ " a deadlock.");
+
+ } else {
+ // Try to pop next character from the buffer.
+ const bool data_exist = popNextFromBuffer(c);
+ if (!data_exist) {
+ // There is no more data so it is really not possible that we're
+ // at MORE_DATA_PROVIDED_EVT.
+ if (ev == MORE_DATA_PROVIDED_EVT) {
+ isc_throw(JSONFeedError,
+ "JSONFeed state indicates that new data have been"
+ " provided to be parsed, but the transaction buffer"
+ " contains no new data.");
+
+ } else {
+ // If there is no more data we should set NEED_MORE_DATA_EVT
+ // event to indicate that new data should be provided.
+ transition(getCurrState(), NEED_MORE_DATA_EVT);
+ }
+ }
+ }
+ return (c);
+}
+
+void
+JSONFeed::invalidEventError(const std::string& handler_name,
+ const unsigned int event) {
+ isc_throw(JSONFeedError, handler_name << ": "
+ << " invalid event " << getEventLabel(static_cast<int>(event)));
+}
+
+void
+JSONFeed::receiveStartHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case START_EVT:
+ switch (c) {
+ case '\t':
+ case '\n':
+ case '\r':
+ case ' ':
+ transition(WHITESPACE_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ return;
+
+ case '{':
+ case '[':
+ output_.push_back(c);
+ ++open_scopes_;
+ transition(INNER_JSON_ST, DATA_READ_OK_EVT);
+ return;
+
+ default:
+ feedFailure("invalid first character " + std::string(1, c));
+ }
+
+ default:
+ invalidEventError("receiveStartHandler", getNextEvent());
+ }
+ }
+}
+
+void
+JSONFeed::whiteSpaceBeforeJSONHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (c) {
+ case '\t':
+ case '\n':
+ case '\r':
+ case ' ':
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ break;
+
+ case '{':
+ case '[':
+ output_.push_back(c);
+ ++open_scopes_;
+ transition(INNER_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ default:
+ feedFailure("invalid character " + std::string(1, c));
+ }
+ }
+}
+
+void
+JSONFeed::innerJSONHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ output_.push_back(c);
+
+ switch(c) {
+ case '{':
+ case '[':
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ ++open_scopes_;
+ break;
+
+ case '}':
+ case ']':
+ if (--open_scopes_ == 0) {
+ transition(JSON_END_ST, FEED_OK_EVT);
+
+ } else {
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ break;
+
+ default:
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ }
+}
+
+void
+JSONFeed::endJSONHandler() {
+ switch (getNextEvent()) {
+ case FEED_OK_EVT:
+ transition(END_ST, END_EVT);
+ break;
+
+ case FEED_FAILED_EVT:
+ abortModel("reading into JSON feed failed");
+ break;
+
+ default:
+ invalidEventError("endJSONHandler", getNextEvent());
+ }
+}
+
+
+} // end of namespace config
+} // end of namespace isc
diff --git a/src/lib/cc/json_feed.h b/src/lib/cc/json_feed.h
new file mode 100644
index 0000000000..11c4dfac12
--- /dev/null
+++ b/src/lib/cc/json_feed.h
@@ -0,0 +1,276 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef JSON_FEED_H
+#define JSON_FEED_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <util/state_model.h>
+#include <boost/shared_ptr.hpp>
+#include <list>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace config {
+
+class JSONFeed;
+
+/// @brief Pointer to the @ref JSONFeed.
+typedef boost::shared_ptr<JSONFeed> JSONFeedPtr;
+
+/// @brief Pointer to the const @ref JSONFeed.
+typedef boost::shared_ptr<const JSONFeed> ConstJSONFeedPtr;
+
+/// @brief A generic exception thrown upon an error in the @ref JSONFeed.
+class JSONFeedError : public Exception {
+public:
+ JSONFeedError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief State model for asynchronous read of data in JSON format.
+///
+/// Kea control channel uses stream sockets for forwarding commands received
+/// by the Kea Control Agent to respective Kea services. The responses may
+/// contain large amounts of data (e.g. lease queries may return thousands
+/// of leases). Such responses rarely fit into a single data buffer and
+/// require multiple calls to receive/read or asynchronous receive/read.
+///
+/// A receiver performing multiple reads from a socket must be able to
+/// locate the boundaries of the command within the data stream. The
+/// @ref JSONFeed state model solves this problem.
+///
+/// When the partial data is read from the stream socket it should be provided
+/// to the @ref JSONFeed using @ref JSONFeed::postBuffer and then the
+/// @ref JSONFeed::poll should be called to start processing the received
+/// data. The actual JSON structure can be preceded by whitespaces. When first
+/// occurrence of one of the '{' or '[' characters is found in the stream it is
+/// considered a beginning of the JSON structure. The model includes an internal
+/// counter of new '{' and '[' occurrences. The counter increases one of these
+/// characters is found. When any of the '}' or ']' is found, the counter
+/// is decreased. When the counter is decreased to 0 it indicates that the
+/// entire JSON structure has been received and processed.
+///
+/// Note that this mechanism doesn't check if the JSON structure is well
+/// formed. It merely detects the end of the JSON structure if this structure
+/// is well formed. The structure is validated when @ref JSONFeed::toElement
+/// is called to retrieve the data structures encapsulated with
+/// @ref isc::data::Element objects.
+class JSONFeed : public util::StateModel {
+public:
+
+ /// @name States supported by the @ref JSONFeed
+ ///
+ //@{
+
+ /// @brief State indicating a beginning of a feed.
+ static const int RECEIVE_START_ST = SM_DERIVED_STATE_MIN + 1;
+
+ /// @brief Skipping whitespaces before actual JSON.
+ static const int WHITESPACE_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 2;
+
+ /// @brief Found first opening brace or square bracket.
+ static const int JSON_START_ST = SM_DERIVED_STATE_MIN + 3;
+
+ /// @brief Parsing JSON.
+ static const int INNER_JSON_ST = SM_DERIVED_STATE_MIN + 4;
+
+ /// @brief Found last closing brace or square bracket.
+ static const int JSON_END_ST = SM_DERIVED_STATE_MIN + 5;
+
+ /// @brief Found opening and closing brace or square bracket.
+ ///
+ /// This doesn't however indicate that the JSON is well formed. It
+ /// only means that matching closing brace or square bracket was
+ /// found.
+ static const int FEED_OK_ST = SM_DERIVED_STATE_MIN + 100;
+
+ /// @brief Invalid syntax detected.
+ ///
+ /// For example, non matching braces or invalid characters found.
+ static const int FEED_FAILED_ST = SM_DERIVED_STATE_MIN + 101;
+
+ //@}
+
+
+ /// @name Events used during data processing.
+ ///
+ //@{
+
+ /// @brief Chunk of data successfully read and parsed.
+ static const int DATA_READ_OK_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+ /// @brief Unable to proceed with parsing until new data is provided.
+ static const int NEED_MORE_DATA_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+ /// @brief New data provided and parsing should continue.
+ static const int MORE_DATA_PROVIDED_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+ /// @brief Found opening brace and the matching closing brace.
+ static const int FEED_OK_EVT = SM_DERIVED_EVENT_MIN + 100;
+
+ /// @brief Invalid syntax detected.
+ static const int FEED_FAILED_EVT = SM_DERIVED_EVENT_MIN + 101;
+
+ //@}
+
+ /// @brief Constructor.
+ JSONFeed();
+
+ /// @brief Initializes state model.
+ ///
+ /// Initializes events and states. It sets the model to @c RECEIVE_START_ST
+ /// and the next event to @c START_EVT.
+ void initModel();
+
+ /// @brief Runs the model as long as data is available.
+ ///
+ /// It processes the input data character by character until it reaches the
+ /// end of the input buffer, in which case it returns. The next event is set
+ /// to @c NEED_MORE_DATA_EVT to indicate the need for providing additional
+ /// data using @ref JSONFeed::postBuffer. This function also returns when
+ /// the end of the JSON structure has been detected or when an error has
+ /// occurred.
+ void poll();
+
+ /// @brief Checks if the model needs additional data to continue.
+ ///
+ /// The caller can use this method to check if the model expects additional
+ /// data to be provided to finish processing input data.
+ ///
+ /// @return true if more data is needed, false otherwise.
+ bool needData() const;
+
+ /// @brief Checks if the data have been successfully processed.
+ bool feedOk() const;
+
+ /// @brief Returns error string when data processing has failed.
+ std::string getErrorMessage() const {
+ return (error_message_);
+ }
+
+ /// @brief Returns processed data as a structure of @ref isc::data::Element
+ /// objects.
+ ///
+ /// @throw JSONFeedError if the received JSON is not well formed.
+ data::ElementPtr toElement() const;
+
+ /// @brief Receives additional data read from a data stream.
+ ///
+ /// A caller invokes this method to pass additional chunk of data received
+ /// from the stream.
+ ///
+ /// @param buf Pointer to a buffer holding additional input data.
+ /// @param buf_size Size of the data in the input buffer.
+ void postBuffer(const void* buf, const size_t buf_size);
+
+
+private:
+
+ /// @brief Make @ref runModel private to make sure that the caller uses
+ /// @ref poll method instead.
+ using StateModel::runModel;
+
+ /// @brief Define events used by the feed.
+ virtual void defineEvents();
+
+ /// @brief Verifies events used by the feed.
+ virtual void verifyEvents();
+
+ /// @brief Defines states of the feed.
+ virtual void defineStates();
+
+ /// @brief Transition to failure state.
+ ///
+ /// This method transitions the model to @ref FEED_FAILED_ST and
+ /// sets next event to FEED_FAILED_EVT.
+ ///
+ /// @param error_msg Error message explaining the failure.
+ void feedFailure(const std::string& error_msg);
+
+ /// @brief A method called when state model fails.
+ ///
+ /// @param explanation Error message explaining the reason for failure.
+ virtual void onModelFailure(const std::string& explanation);
+
+ /// @brief Retrieves next byte of data from the buffer.
+ ///
+ /// During normal operation, when there is no more data in the buffer,
+ /// the NEED_MORE_DATA_EVT is set as next event to signal the need for
+ /// calling @ref JSONFeed::postBuffer.
+ ///
+ /// @throw JSONFeedError If current event is already set to
+ /// NEED_MORE_DATA_EVT or MORE_DATA_PROVIDED_EVT. In the former case, it
+ /// indicates that the caller failed to provide new data using
+ /// @ref JSONFeed::postBuffer. The latter case is highly unlikely
+ /// as it indicates that no new data were provided but the state of the
+ /// parser was changed from NEED_MORE_DATA_EVT or the data were provided
+ /// but the data buffer is empty. In both cases, it is a programming
+ /// error.
+ char getNextFromBuffer();
+
+ /// @brief This method is called when invalid event occurred in a particular
+ /// state.
+ ///
+ /// This method simply throws @ref JSONFeedError informing about invalid
+ /// event occurring for the particular state. The error message includes
+ /// the name of the handler in which the exception has been thrown.
+ /// It also includes the event which caused the exception.
+ ///
+ /// @param handler_name Name of the handler in which the exception is
+ /// thrown.
+ /// @param event An event which caused the exception.
+ ///
+ /// @throw JSONFeedError.
+ void invalidEventError(const std::string& handler_name,
+ const unsigned int event);
+
+ /// @brief Tries to read next byte from buffer.
+ ///
+ /// @param [out] next A reference to the variable where read data should be
+ /// stored.
+ ///
+ /// @return true if character was successfully read, false otherwise.
+ bool popNextFromBuffer(char& next);
+
+ /// @name State handlers.
+ ///
+ //@{
+
+ /// @brief Handler for RECEIVE_START_ST.
+ void receiveStartHandler();
+
+ /// @brief Handler for WHITESPACE_BEFORE_JSON_ST.
+ void whiteSpaceBeforeJSONHandler();
+
+ /// @brief Handle for the FIRST_BRACE_ST.
+ void innerJSONHandler();
+
+ /// @brief Handler for the JSON_END_ST.
+ void endJSONHandler();
+
+ //@}
+
+ /// @brief Internal buffer from which the feed reads data.
+ std::list<char> buffer_;
+
+ /// @brief Error message set by @ref onModelFailure.
+ std::string error_message_;
+
+ /// @brief A counter increased when '{' or '[' is found and decreased when
+ /// '}' or ']' is found in the stream.
+ uint64_t open_scopes_;
+
+ /// @brief Holds processed data.
+ std::string output_;
+};
+
+} // end of namespace config
+} // end of namespace isc
+
+#endif // JSON_FEED_H
diff --git a/src/lib/cc/simple_parser.cc b/src/lib/cc/simple_parser.cc
new file mode 100644
index 0000000000..a8defad781
--- /dev/null
+++ b/src/lib/cc/simple_parser.cc
@@ -0,0 +1,213 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <cc/simple_parser.h>
+#include <asiolink/io_address.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <cc/data.h>
+#include <string>
+
+using namespace std;
+using namespace isc::asiolink;
+using isc::dhcp::DhcpConfigError;
+
+namespace isc {
+namespace data {
+
+std::string
+SimpleParser::getString(isc::data::ConstElementPtr scope, const std::string& name) {
+ ConstElementPtr x = scope->get(name);
+ if (!x) {
+ isc_throw(DhcpConfigError,
+ "missing parameter '" << name << "' ("
+ << scope->getPosition() << ")");
+ }
+ if (x->getType() != Element::string) {
+ isc_throw(DhcpConfigError,
+ "invalid type specified for parameter '" << name
+ << "' (" << x->getPosition() << ")");
+ }
+
+ return (x->stringValue());
+}
+
+int64_t
+SimpleParser::getInteger(isc::data::ConstElementPtr scope, const std::string& name) {
+ ConstElementPtr x = scope->get(name);
+ if (!x) {
+ isc_throw(DhcpConfigError,
+ "missing parameter '" << name << "' ("
+ << scope->getPosition() << ")");
+ }
+ if (x->getType() != Element::integer) {
+ isc_throw(DhcpConfigError,
+ "invalid type specified for parameter '" << name
+ << "' (" << x->getPosition() << ")");
+ }
+
+ return (x->intValue());
+}
+
+bool
+SimpleParser::getBoolean(isc::data::ConstElementPtr scope, const std::string& name) {
+ ConstElementPtr x = scope->get(name);
+ if (!x) {
+ isc_throw(DhcpConfigError,
+ "missing parameter '" << name << "' ("
+ << scope->getPosition() << ")");
+ }
+ if (x->getType() != Element::boolean) {
+ isc_throw(DhcpConfigError,
+ "invalid type specified for parameter '" << name
+ << "' (" << x->getPosition() << ")");
+ }
+
+ return (x->boolValue());
+}
+
+IOAddress
+SimpleParser::getAddress(const ConstElementPtr& scope,
+ const std::string& name) {
+ std::string str = getString(scope, name);
+ try {
+ return (IOAddress(str));
+ } catch (const std::exception& e) {
+ isc_throw(DhcpConfigError, "Failed to convert '" << str
+ << "' to address: " << e.what() << "("
+ << getPosition(name, scope) << ")");
+ }
+}
+
+const data::Element::Position&
+SimpleParser::getPosition(const std::string& name, const data::ConstElementPtr parent) {
+ if (!parent) {
+ return (data::Element::ZERO_POSITION());
+ }
+ ConstElementPtr elem = parent->get(name);
+ if (!elem) {
+ return (parent->getPosition());
+ }
+ return (elem->getPosition());
+}
+
+size_t SimpleParser::setDefaults(isc::data::ElementPtr scope,
+ const SimpleDefaults& default_values) {
+ size_t cnt = 0;
+
+ // This is the position representing a default value. As the values
+ // we're inserting here are not present in whatever the config file
+ // came from, we need to make sure it's clearly labeled as default.
+ const Element::Position pos("<default-value>", 0, 0);
+
+ // Let's go over all parameters we have defaults for.
+ BOOST_FOREACH(SimpleDefault def_value, default_values) {
+
+ // Try if such a parameter is there. If it is, let's
+ // skip it, because user knows best *cough*.
+ ConstElementPtr x = scope->get(string(def_value.name_));
+ if (x) {
+ // There is such a value already, skip it.
+ continue;
+ }
+
+ // There isn't such a value defined, let's create the default
+ // value...
+ switch (def_value.type_) {
+ case Element::string: {
+ x.reset(new StringElement(def_value.value_, pos));
+ break;
+ }
+ case Element::integer: {
+ try {
+ int int_value = boost::lexical_cast<int>(def_value.value_);
+ x.reset(new IntElement(int_value, pos));
+ }
+ catch (const std::exception& ex) {
+ isc_throw(BadValue, "Internal error. Integer value expected for: "
+ << def_value.name_ << ", value is: "
+ << def_value.value_ );
+ }
+
+ break;
+ }
+ case Element::boolean: {
+ bool bool_value;
+ if (def_value.value_ == string("true")) {
+ bool_value = true;
+ } else if (def_value.value_ == string("false")) {
+ bool_value = false;
+ } else {
+ isc_throw(DhcpConfigError,
+ "Internal error. Boolean value specified as "
+ << def_value.value_ << ", expected true or false");
+ }
+ x.reset(new BoolElement(bool_value, pos));
+ break;
+ }
+ case Element::real: {
+ double dbl_value = boost::lexical_cast<double>(def_value.value_);
+ x.reset(new DoubleElement(dbl_value, pos));
+ break;
+ }
+ default:
+ // No default values for null, list or map
+ isc_throw(DhcpConfigError,
+ "Internal error. Incorrect default value type.");
+ }
+
+ // ... and insert it into the provided Element tree.
+ scope->set(def_value.name_, x);
+ ++cnt;
+ }
+
+ return (cnt);
+}
+
+size_t
+SimpleParser::setListDefaults(isc::data::ConstElementPtr list,
+ const SimpleDefaults& default_values) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ElementPtr entry, list->listValue()) {
+ cnt += setDefaults(entry, default_values);
+ }
+ return (cnt);
+}
+
+size_t
+SimpleParser::deriveParams(isc::data::ConstElementPtr parent,
+ isc::data::ElementPtr child,
+ const ParamsList& params) {
+ if ( (parent->getType() != Element::map) ||
+ (child->getType() != Element::map)) {
+ return (0);
+ }
+
+ size_t cnt = 0;
+ BOOST_FOREACH(string param, params) {
+ ConstElementPtr x = parent->get(param);
+ if (!x) {
+ // Parent doesn't define this parameter, so there's
+ // nothing to derive from
+ continue;
+ }
+
+ if (child->get(param)) {
+ // Child defines this parameter already. There's
+ // nothing to do here.
+ continue;
+ }
+
+ // Copy the parameters to the child scope.
+ child->set(param, x);
+ cnt++;
+ }
+
+ return (cnt);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/cc/simple_parser.h b/src/lib/cc/simple_parser.h
new file mode 100644
index 0000000000..b3aac37b32
--- /dev/null
+++ b/src/lib/cc/simple_parser.h
@@ -0,0 +1,265 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SIMPLE_PARSER_H
+#define SIMPLE_PARSER_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <cc/dhcp_config_error.h>
+#include <vector>
+#include <string>
+#include <stdint.h>
+#include <limits>
+
+namespace isc {
+namespace data {
+
+/// This array defines a single entry of default values
+struct SimpleDefault {
+ SimpleDefault(const char* name, isc::data::Element::types type, const char* value)
+ :name_(name), type_(type), value_(value) {}
+ std::string name_;
+ const isc::data::Element::types type_;
+ const char* value_;
+};
+
+/// This specifies all default values in a given scope (e.g. a subnet)
+typedef std::vector<SimpleDefault> SimpleDefaults;
+
+/// This defines a list of all parameters that are derived (or inherited) between
+/// contexts
+typedef std::vector<std::string> ParamsList;
+
+
+/// @brief A simple parser
+///
+/// This class is intended to be a simpler replacement for @ref
+/// isc::dhcp::DhcpConfigParser. This class has been initially created to
+/// facilitate DHCPv4 and DHCPv6 servers' configuration parsing. Thus examples
+/// provided herein are related to DHCP configuration. Nevertheless, this is a
+/// generic class to be used in other modules too.
+///
+/// The simplification comes from several factors:
+/// - no build/commit nonsense. There's a single step:
+/// CfgStorage parse(ConstElementPtr json)
+/// that converts JSON configuration into an object and returns it.
+/// - no state kept. This greatly simplifies the parsers (no contexts, no child
+/// parsers list, no separate storage for uint32, strings etc. In fact,
+/// this base class is purely static. However, some derived classes may store
+/// some state. Implementors are advised to store as little state as possible.
+/// - no optional parameters (all are mandatory). This simplifies the parser,
+/// but introduces a new step before parsing where we insert the default
+/// values into client configuration before parsing. This is actually a good
+/// thing, because we now have a clear picture of the default parameters as
+/// they're defined in a single place (the DhcpConfigParser had the defaults
+/// spread out in multiple files in multiple directories).
+class SimpleParser {
+ public:
+
+ /// @brief Derives (inherits) parameters from parent scope to a child
+ ///
+ /// This method derives parameters from the parent scope to the child,
+ /// if there are no values specified in the child scope. For example,
+ /// this method can be used to derive timers from global scope (e.g. for
+ /// the whole DHCPv6 server) to a subnet scope. This method checks
+ /// if the child scope doesn't have more specific values defined. If
+ /// it doesn't, then the value from parent scope is copied over.
+ ///
+ /// @param parent scope to copy from (e.g. global)
+ /// @param child scope to copy from (e.g. subnet)
+ /// @param params names of the parameters to copy
+ /// @return number of parameters copied
+ static size_t deriveParams(isc::data::ConstElementPtr parent,
+ isc::data::ElementPtr child,
+ const ParamsList& params);
+
+ /// @brief Sets the default values
+ ///
+ /// This method sets the default values for parameters that are not
+ /// defined. The list of default values is specified by default_values.
+ /// If not present, those will be inserted into the scope. If
+ /// a parameter is already present, the default value will not
+ /// be inserted.
+ ///
+ /// @param scope default values will be inserted here
+ /// @param default_values list of default values
+ /// @return number of parameters inserted
+ static size_t setDefaults(isc::data::ElementPtr scope,
+ const SimpleDefaults& default_values);
+
+ /// @brief Sets the default values for all entries in a list
+ ///
+ /// This is a simple utility method that iterates over all
+ /// parameters in a list and calls setDefaults for each
+ /// entry.
+ ///
+ /// @param list list to be iterated over
+ /// @param default_values list of default values
+ /// @return number of parameters inserted
+ static size_t setListDefaults(isc::data::ConstElementPtr list,
+ const SimpleDefaults& default_values);
+
+ /// @brief Utility method that returns position of an element
+ ///
+ /// It's mostly useful for logging. If the element is missing
+ /// the parent position is returned or ZERO_POSITION if parent
+ /// is null.
+ ///
+ /// @param name position of that element will be returned
+ /// @param parent parent element (optional)
+ /// @return position of the element specified.
+ static const data::Element::Position&
+ getPosition(const std::string& name, const data::ConstElementPtr parent);
+
+ /// @brief Returns a string parameter from a scope
+ ///
+ /// Unconditionally returns a parameter.
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a string value of the parameter
+ /// @throw DhcpConfigError if the parameter is not there or is not of
+ /// appropriate type
+ static std::string getString(isc::data::ConstElementPtr scope,
+ const std::string& name);
+
+ /// @brief Returns an integer parameter from a scope
+ ///
+ /// Unconditionally returns a parameter.
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return an integer value of the parameter
+ /// @throw DhcpConfigError if the parameter is not there or is not of
+ /// appropriate type
+ static int64_t getInteger(isc::data::ConstElementPtr scope,
+ const std::string& name);
+
+ /// @brief Returns a boolean parameter from a scope
+ ///
+ /// Unconditionally returns a parameter.
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a boolean value of the parameter
+ /// @throw DhcpConfigError if the parameter is not there or is not of
+ /// appropriate type
+ static bool getBoolean(isc::data::ConstElementPtr scope,
+ const std::string& name);
+
+
+ /// @brief Returns a IOAddress parameter from a scope
+ ///
+ /// Unconditionally returns a parameter.
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return an IOAddress representing the value of the parameter
+ /// @throw DhcpConfigError if the parameter is not there or is not of
+ /// appropriate type (or its conversion to IOAddress fails due to not
+ /// being a proper address).
+ static isc::asiolink::IOAddress
+ getAddress(const ConstElementPtr& scope, const std::string& name);
+
+protected:
+
+ /// @brief Returns an integer value with range checking from a scope
+ ///
+ /// This template should be instantiated in parsers when useful
+ ///
+ /// @tparam int_type the integer type e.g. uint32_t
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter for error report
+ /// @return a value of int_type
+ /// @throw DhcpConfigError if the parameter is not there, is not of
+ /// appropriate type or is out of type value range
+ template <typename int_type> int_type
+ getIntType(isc::data::ConstElementPtr scope,
+ const std::string& name) {
+ int64_t val_int = getInteger(scope, name);
+ if ((val_int < std::numeric_limits<int_type>::min()) ||
+ (val_int > std::numeric_limits<int_type>::max())) {
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "out of range value (" << val_int
+ << ") specified for parameter '" << name
+ << "' (" << getPosition(name, scope) << ")");
+ }
+ return (static_cast<int_type>(val_int));
+ }
+
+ /// @brief Returns a converted value from a scope
+ ///
+ /// This template should be instantiated in parsers when useful
+ ///
+ /// @tparam target_type the type of the result
+ /// @tparam convert the conversion function std::string -> target_type
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter for error report
+ /// @param type_name name of target_type for error report
+ /// @return a converted value of target_type
+ /// @throw DhcpConfigError if the parameter is not there, is not of
+ /// appropriate type or can not be converted
+ template <typename target_type,
+ target_type convert(const std::string&)> target_type
+ getAndConvert(isc::data::ConstElementPtr scope,
+ const std::string& name,
+ const std::string& type_name) {
+ std::string str = getString(scope, name);
+ try {
+ return (convert(str));
+ } catch (const std::exception&) {
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "invalid " << type_name << " (" << str
+ << ") specified for parameter '" << name
+ << "' (" << getPosition(name, scope) << ")");
+ }
+ }
+
+public:
+ /// @brief Returns a value converted to uint32_t
+ ///
+ /// Instantiation of getIntType() to uint32_t
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return an uint32_t value
+ /// @throw isc::dhcp::DhcpConfigError when it is not an uint32_t
+ uint32_t getUint32(isc::data::ConstElementPtr scope,
+ const std::string& name) {
+ return (getIntType<uint32_t>(scope, name));
+ }
+
+ /// @brief Returns a value converted to uint16_t
+ ///
+ /// Instantiation of getIntType() to uint16_t
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return an uint16_t value
+ /// @throw isc::dhcp::DhcpConfigError when it is not an uint16_t
+ uint16_t getUint16(isc::data::ConstElementPtr scope,
+ const std::string& name) {
+ return (getIntType<uint16_t>(scope, name));
+ }
+
+ /// @brief Get an uint8_t value
+ ///
+ /// Instantiation of getIntType() to uint8_t
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return uint8_t value
+ /// @throw isc::dhcp::DhcpConfigError when it is not an uint8_t
+ uint8_t getUint8(ConstElementPtr scope, const std::string& name) {
+ return (getIntType<uint8_t>(scope, name));
+ }
+};
+
+};
+};
+
+#endif
diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am
index 67d85bab26..f07e9c75cb 100644
--- a/src/lib/cc/tests/Makefile.am
+++ b/src/lib/cc/tests/Makefile.am
@@ -15,12 +15,16 @@ TESTS =
if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = command_interpreter_unittests.cc data_unittests.cc
-run_unittests_SOURCES += data_file_unittests.cc run_unittests.cc
+run_unittests_SOURCES += data_file_unittests.cc
+run_unittests_SOURCES += json_feed_unittests.cc
+run_unittests_SOURCES += run_unittests.cc
+run_unittests_SOURCES += simple_parser_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(top_builddir)/src/lib/cc/libkea-cc.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
diff --git a/src/lib/cc/tests/command_interpreter_unittests.cc b/src/lib/cc/tests/command_interpreter_unittests.cc
index 170da754c7..df381d0f46 100644
--- a/src/lib/cc/tests/command_interpreter_unittests.cc
+++ b/src/lib/cc/tests/command_interpreter_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -87,6 +87,19 @@ TEST(CommandInterpreterTest, parseAnswer) {
EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", arg->str());
}
+// This checks whether we can convert an answer to easily printable form.
+TEST(CommandInterpreterTest, answerToText) {
+ ConstElementPtr answer;
+
+ // Doing jolly good here.
+ answer = el("{ \"result\": 0 }");
+ EXPECT_EQ("success(0)", answerToText(answer));
+
+ // Sometimes things don't go according to plan.
+ answer = el("{ \"result\": 1, \"text\": \"ho lee fuk sum ting wong\" }");
+ EXPECT_EQ("failure(1), text=ho lee fuk sum ting wong", answerToText(answer));
+}
+
// This test checks whether createCommand function is able to create commands
// with and without parameters.
TEST(CommandInterpreterTest, createCommand) {
diff --git a/src/lib/cc/tests/data_file_unittests.cc b/src/lib/cc/tests/data_file_unittests.cc
index eea89c5db2..8471849b6e 100644
--- a/src/lib/cc/tests/data_file_unittests.cc
+++ b/src/lib/cc/tests/data_file_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -16,13 +16,13 @@ using namespace isc::data;
namespace {
-/// @brief Test class for testing Deamon class
+/// @brief Test class for testing Daemon class
class DataFileTest : public ::testing::Test {
public:
/// @brief writes specified text to a file
///
- /// That is an auxilliary funtion used in fileRead() tests.
+ /// That is an auxiliary function used in fileRead() tests.
///
/// @param content text to be written to disk
void writeFile(const std::string& content) {
diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc
index 6b1a657e5c..e1455ec56f 100644
--- a/src/lib/cc/tests/data_unittests.cc
+++ b/src/lib/cc/tests/data_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,10 +6,12 @@
#include <gtest/gtest.h>
#include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
#include <boost/assign/std/vector.hpp>
#include <climits>
#include <cc/data.h>
+#include <util/unittests/check_valgrind.h>
using namespace isc::data;
@@ -100,7 +102,7 @@ TEST(Element, from_and_to_json) {
BOOST_FOREACH(const std::string& s, sv) {
// Test two types of fromJSON(): with string and istream.
- for (int i = 0; i < 2; ++i) {
+ for (unsigned i = 0; i < 2; ++i) {
// test << operator, which uses Element::str()
if (i == 0) {
el = Element::fromJSON(s);
@@ -131,11 +133,13 @@ TEST(Element, from_and_to_json) {
sv.push_back("{1}");
//ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err");
//std::cout << ep << std::endl;
- sv.push_back("\n\nTru");
+ sv.push_back("\n\nTrue");
+ sv.push_back("\n\ntru");
sv.push_back("{ \n \"aaa\nbbb\"err:");
- sv.push_back("{ \t\n \"aaa\nbbb\"\t\n\n:\n True, \"\\\"");
+ sv.push_back("{ \t\n \"aaa\nbbb\"\t\n\n:\n true, \"\\\"");
sv.push_back("{ \"a\": None}");
sv.push_back("");
+ sv.push_back("NULL");
sv.push_back("nul");
sv.push_back("hello\"foobar\"");
sv.push_back("\"foobar\"hello");
@@ -178,12 +182,6 @@ TEST(Element, from_and_to_json) {
EXPECT_EQ("0.01", Element::fromJSON("1.0e-2")->str());
EXPECT_EQ("0.012", Element::fromJSON("1.2e-2")->str());
EXPECT_EQ("0.012", Element::fromJSON("1.2E-2")->str());
- EXPECT_EQ("null", Element::fromJSON("Null")->str());
- EXPECT_EQ("null", Element::fromJSON("NULL")->str());
- EXPECT_EQ("false", Element::fromJSON("False")->str());
- EXPECT_EQ("false", Element::fromJSON("FALSE")->str());
- EXPECT_EQ("true", Element::fromJSON("True")->str());
- EXPECT_EQ("true", Element::fromJSON("TRUE")->str());
EXPECT_EQ("\"\"", Element::fromJSON(" \n \t \r \f \b \"\" \n \f \t \r \b")->str());
EXPECT_EQ("{ }", Element::fromJSON("{ \n \r \t \b \f }")->str());
EXPECT_EQ("[ ]", Element::fromJSON("[ \n \r \f \t \b ]")->str());
@@ -210,7 +208,7 @@ testGetValueInt() {
double d;
bool b;
std::string s;
- std::vector<ConstElementPtr> v;
+ std::vector<ElementPtr> v;
std::map<std::string, ConstElementPtr> m;
el = Element::create(1);
@@ -270,7 +268,7 @@ testGetValueDouble() {
double d;
bool b;
std::string s;
- std::vector<ConstElementPtr> v;
+ std::vector<ElementPtr> v;
std::map<std::string, ConstElementPtr> m;
el = Element::create(1.1);
@@ -297,7 +295,7 @@ testGetValueBool() {
double d;
bool b;
std::string s;
- std::vector<ConstElementPtr> v;
+ std::vector<ElementPtr> v;
std::map<std::string, ConstElementPtr> m;
el = Element::create(true);
@@ -324,7 +322,7 @@ testGetValueString() {
double d;
bool b;
std::string s;
- std::vector<ConstElementPtr> v;
+ std::vector<ElementPtr> v;
std::map<std::string, ConstElementPtr> m;
el = Element::create("foo");
@@ -351,7 +349,7 @@ testGetValueList() {
double d;
bool b;
std::string s;
- std::vector<ConstElementPtr> v;
+ std::vector<ElementPtr> v;
std::map<std::string, ConstElementPtr> m;
el = Element::createList();
@@ -378,7 +376,7 @@ testGetValueMap() {
double d;
bool b;
std::string s;
- std::vector<ConstElementPtr> v;
+ std::vector<ElementPtr> v;
std::map<std::string, ConstElementPtr> m;
el = Element::createMap();
@@ -406,7 +404,7 @@ TEST(Element, create_and_value_throws) {
double d = 0.0;
bool b = false;
std::string s("asdf");
- std::vector<ConstElementPtr> v;
+ std::vector<ElementPtr> v;
std::map<std::string, ConstElementPtr> m;
ConstElementPtr tmp;
@@ -559,6 +557,35 @@ TEST(Element, escape) {
EXPECT_NO_THROW(Element::fromJSON("\"\\\"\\\"\""));
// A whitespace test
EXPECT_NO_THROW(Element::fromJSON("\" \n \r \t \f \n \n \t\""));
+ // Escape for forward slash is optional
+ ASSERT_NO_THROW(Element::fromJSON("\"foo\\/bar\""));
+ EXPECT_EQ("foo/bar", Element::fromJSON("\"foo\\/bar\"")->stringValue());
+ // Control characters
+ StringElement bell("foo\abar");
+ EXPECT_EQ("\"foo\\u0007bar\"", bell.str());
+}
+
+// This test verifies that strings are copied.
+TEST(Element, stringCopy) {
+ // StringElement constructor copies its string argument.
+ std::string foo = "foo";
+ ElementPtr elem = ElementPtr(new StringElement(foo));
+ EXPECT_EQ(foo, elem->stringValue());
+ foo[1] = 'O';
+ EXPECT_EQ("fOo", foo);
+ EXPECT_NE(foo, elem->stringValue());
+
+ // Map keys are copied too.
+ ElementPtr map = ElementPtr(new MapElement());
+ std::string bar = "bar";
+ map->set(bar, ElementPtr(new IntElement(1)));
+ ConstElementPtr item = map->get("bar");
+ ASSERT_TRUE(item);
+ EXPECT_EQ(1, item->intValue());
+ bar[0] = 'B';
+ EXPECT_EQ("Bar", bar);
+ EXPECT_TRUE(map->get("bar"));
+ EXPECT_FALSE(map->get(bar));
}
// This test verifies that a backslash can be used in element content
@@ -659,6 +686,12 @@ TEST(Element, MapElement) {
el->set(long_maptag, Element::create("bar"));
EXPECT_EQ("bar", el->find(long_maptag)->stringValue());
+ // Null pointer value
+ el.reset(new MapElement());
+ ConstElementPtr null_ptr;
+ el->set("value", null_ptr);
+ EXPECT_FALSE(el->get("value"));
+ EXPECT_EQ("{ \"value\": None }", el->str());
}
TEST(Element, to_and_from_wire) {
@@ -709,35 +742,35 @@ TEST(Element, equals) {
EXPECT_NE(*efs("1"), *efs("2"));
EXPECT_NE(*efs("1"), *efs("\"1\""));
EXPECT_NE(*efs("1"), *efs("[]"));
- EXPECT_NE(*efs("1"), *efs("True"));
+ EXPECT_NE(*efs("1"), *efs("true"));
EXPECT_NE(*efs("1"), *efs("{}"));
EXPECT_EQ(*efs("1.1"), *efs("1.1"));
EXPECT_NE(*efs("1.0"), *efs("1"));
EXPECT_NE(*efs("1.1"), *efs("\"1\""));
EXPECT_NE(*efs("1.1"), *efs("[]"));
- EXPECT_NE(*efs("1.1"), *efs("True"));
+ EXPECT_NE(*efs("1.1"), *efs("true"));
EXPECT_NE(*efs("1.1"), *efs("{}"));
- EXPECT_EQ(*efs("True"), *efs("True"));
- EXPECT_NE(*efs("True"), *efs("False"));
- EXPECT_NE(*efs("True"), *efs("1"));
- EXPECT_NE(*efs("True"), *efs("\"1\""));
- EXPECT_NE(*efs("True"), *efs("[]"));
- EXPECT_NE(*efs("True"), *efs("{}"));
+ EXPECT_EQ(*efs("true"), *efs("true"));
+ EXPECT_NE(*efs("true"), *efs("false"));
+ EXPECT_NE(*efs("true"), *efs("1"));
+ EXPECT_NE(*efs("true"), *efs("\"1\""));
+ EXPECT_NE(*efs("true"), *efs("[]"));
+ EXPECT_NE(*efs("true"), *efs("{}"));
EXPECT_EQ(*efs("\"foo\""), *efs("\"foo\""));
EXPECT_NE(*efs("\"foo\""), *efs("\"bar\""));
EXPECT_NE(*efs("\"foo\""), *efs("1"));
EXPECT_NE(*efs("\"foo\""), *efs("\"1\""));
- EXPECT_NE(*efs("\"foo\""), *efs("True"));
+ EXPECT_NE(*efs("\"foo\""), *efs("true"));
EXPECT_NE(*efs("\"foo\""), *efs("[]"));
EXPECT_NE(*efs("\"foo\""), *efs("{}"));
EXPECT_EQ(*efs("[]"), *efs("[]"));
EXPECT_EQ(*efs("[ 1, 2, 3 ]"), *efs("[ 1, 2, 3 ]"));
- EXPECT_EQ(*efs("[ \"a\", [ True, 1], 2.2 ]"), *efs("[ \"a\", [ True, 1], 2.2 ]"));
- EXPECT_NE(*efs("[ \"a\", [ True, 1], 2.2 ]"), *efs("[ \"a\", [ True, 2], 2.2 ]"));
+ EXPECT_EQ(*efs("[ \"a\", [ true, 1], 2.2 ]"), *efs("[ \"a\", [ true, 1], 2.2 ]"));
+ EXPECT_NE(*efs("[ \"a\", [ true, 1], 2.2 ]"), *efs("[ \"a\", [ true, 2], 2.2 ]"));
EXPECT_NE(*efs("[]"), *efs("[1]"));
EXPECT_NE(*efs("[]"), *efs("1"));
EXPECT_NE(*efs("[]"), *efs("\"1\""));
@@ -861,8 +894,9 @@ TEST(Element, constRemoveIdentical) {
c = Element::fromJSON("{ \"a\": 1 }");
EXPECT_EQ(*removeIdentical(a, b), *c);
- EXPECT_THROW(removeIdentical(Element::create(1), Element::create(2)),
- TypeError);
+ // removeIdentical() is overloaded so force the first argument to const
+ ConstElementPtr bad = Element::create(1);
+ EXPECT_THROW(removeIdentical(bad, Element::create(2)), TypeError);
}
TEST(Element, merge) {
@@ -951,6 +985,152 @@ TEST(Element, merge) {
}
+// This test checks copy.
+TEST(Element, copy) {
+ // Null pointer
+ ElementPtr elem;
+ EXPECT_THROW(copy(elem, 0), isc::BadValue);
+ EXPECT_THROW(copy(elem), isc::BadValue);
+ EXPECT_THROW(copy(elem, -1), isc::BadValue);
+
+ // Basic types
+ elem.reset(new IntElement(1));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("1")));
+ EXPECT_EQ("1", elem->str());
+ ElementPtr copied;
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+
+ elem.reset(new DoubleElement(1.0));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("1.0")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+
+ elem.reset(new BoolElement(true));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("true")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+
+ elem.reset(new NullElement());
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("null")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+
+ elem.reset(new StringElement("foo"));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("\"foo\"")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+ ASSERT_NO_THROW(elem->setValue(std::string("bar")));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("\"bar\"")));
+ EXPECT_FALSE(elem->equals(*copied));
+
+ elem.reset(new ListElement());
+ ElementPtr item = ElementPtr(new IntElement(1));
+ elem->add(item);
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("[ 1 ]")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+ ElementPtr deep;
+ ASSERT_NO_THROW(deep = copy(elem));
+ EXPECT_TRUE(elem->equals(*deep));
+ ASSERT_NO_THROW(item = elem->getNonConst(0));
+ ASSERT_NO_THROW(item->setValue(2));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("[ 2 ]")));
+ EXPECT_TRUE(elem->equals(*copied));
+ EXPECT_FALSE(elem->equals(*deep));
+
+ elem.reset(new MapElement());
+ item.reset(new StringElement("bar"));
+ elem->set("foo", item);
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("{ \"foo\": \"bar\" }")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+ ASSERT_NO_THROW(deep = copy(elem));
+ EXPECT_TRUE(elem->equals(*deep));
+ ASSERT_NO_THROW(item->setValue(std::string("Bar")));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("{ \"foo\": \"Bar\" }")));
+ EXPECT_TRUE(elem->equals(*copied));
+ EXPECT_FALSE(elem->equals(*deep));
+
+ // Complex example
+ std::string input = "{ \n"
+ "\"integer\": 1,\n"
+ "\"double\": 1.0,\n"
+ "\"boolean\": true,\n"
+ "\"null\": null,\n"
+ "\"string\": \"foobar\",\n"
+ "\"list\": [ 1, 2 ],\n"
+ "\"map\": { \"foo\": \"bar\" } }\n";
+ ConstElementPtr complex;
+ ASSERT_NO_THROW(complex = Element::fromJSON(input));
+ ASSERT_NO_THROW(copied = copy(complex, 0));
+ EXPECT_TRUE(copied->equals(*complex));
+ ASSERT_NO_THROW(deep = copy(complex));
+ EXPECT_TRUE(deep->equals(*complex));
+ ElementPtr shallow;
+ ASSERT_NO_THROW(shallow = copy(complex, 1));
+ EXPECT_TRUE(shallow->equals(*complex));
+ // Try to modify copies
+ ASSERT_NO_THROW(item = deep->get("list")->getNonConst(1));
+ ASSERT_NO_THROW(item->setValue(3));
+ EXPECT_FALSE(deep->equals(*complex));
+ EXPECT_TRUE(shallow->equals(*complex));
+ ASSERT_NO_THROW(item = boost::const_pointer_cast<Element>(shallow->get("string")));
+ ASSERT_NO_THROW(item->setValue(std::string("FooBar")));
+ EXPECT_FALSE(shallow->equals(*complex));
+ EXPECT_TRUE(copied->equals(*complex));
+}
+
+// This test checks the isEquivalent function.
+TEST(Element, isEquivalent) {
+ // All are different but a is equivalent to b
+ string texta = "{ \"a\": 1, \"b\": [ ], \"c\": [ 1, 1, 2 ] }";
+ string textb = "{ \"b\": [ ], \"a\": 1, \"c\": [ 1, 2, 1 ] }";
+ string textc = "{ \"a\": 2, \"b\": [ ], \"c\": [ 1, 1, 2 ] }";
+ string textd = "{ \"a\": 1, \"c\": [ ], \"b\": [ 1, 1, 2 ] }";
+ string texte = "{ \"a\": 1, \"b\": [ ], \"c\": [ 1, 2, 2 ] }";
+
+ ElementPtr a = Element::fromJSON(texta);
+ ElementPtr b = Element::fromJSON(textb);
+ ElementPtr c = Element::fromJSON(textc);
+ ElementPtr d = Element::fromJSON(textd);
+ ElementPtr e = Element::fromJSON(texte);
+
+ EXPECT_TRUE(isEquivalent(a, b));
+ EXPECT_NE(a, b);
+ EXPECT_FALSE(isEquivalent(a, c));
+ EXPECT_FALSE(isEquivalent(a, d));
+ EXPECT_FALSE(isEquivalent(a, e));
+
+ // Verifies isEquivalent handles cycles
+ if (isc::util::unittests::runningOnValgrind()) {
+ ElementPtr l = Element::createList();
+ l->add(l);
+ EXPECT_THROW(isEquivalent(l, l), isc::BadValue);
+ }
+}
+
+
+// This test checks the pretty print function.
+TEST(Element, prettyPrint) {
+
+ // default step is 2, order is alphabetic, no \n at the end
+ string text = "{\n"
+ " \"boolean\": true,\n"
+ " \"empty-list\": [ ],\n"
+ " \"empty-map\": { },\n"
+ " \"integer\": 1,\n"
+ " \"list\": [ 1, 2, 3 ],\n"
+ " \"map\": {\n"
+ " \"item\": null\n"
+ " },\n"
+ " \"string\": \"foobar\"\n"
+ "}";
+ ElementPtr json = Element::fromJSON(text);
+ string pprinted = prettyPrint(json);
+ EXPECT_EQ(text, pprinted);
+}
+
// This test checks whether it is possible to ignore comments. It also checks
// that the comments are ignored only when told to.
TEST(Element, preprocessor) {
@@ -1003,6 +1183,10 @@ TEST(Element, preprocessor) {
EXPECT_THROW(Element::fromJSON(dbl_head_comment), JSONError);
EXPECT_THROW(Element::fromJSON(dbl_mid_comment), JSONError);
EXPECT_THROW(Element::fromJSON(dbl_tail_comment), JSONError);
+
+ // For coverage
+ std::istringstream iss(no_comment);
+ EXPECT_TRUE(exp->equals(*Element::fromJSON(iss, true)));
}
TEST(Element, getPosition) {
diff --git a/src/lib/cc/tests/json_feed_unittests.cc b/src/lib/cc/tests/json_feed_unittests.cc
new file mode 100644
index 0000000000..e289757d00
--- /dev/null
+++ b/src/lib/cc/tests/json_feed_unittests.cc
@@ -0,0 +1,174 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <cc/data.h>
+#include <cc/json_feed.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace isc::config;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Test fixture class for @ref JSONFeed class.
+class JSONFeedTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes @ref json_map_ and @ref json_list_ which hold reference
+ /// JSON structures.
+ JSONFeedTest()
+ : json_map_(), json_list_() {
+ ElementPtr m = Element::fromJSON(createJSON());
+ ElementPtr l = Element::createList();
+ l->add(m);
+ json_map_ = m;
+ json_list_ = l;
+ }
+
+ /// @brief Creates a JSON map holding 20 elements.
+ ///
+ /// Each map value is a list of 20 elements.
+ std::string createJSON() const {
+ // Create a list of 20 elements.
+ ElementPtr list_element = Element::createList();
+ for (unsigned i = 0; i < 20; ++i) {
+ std::ostringstream s;
+ s << "list_element" << i;
+ list_element->add(Element::create(s.str()));
+ }
+
+ // Create a map of 20 elements. Each map element holds a list
+ // of 20 elements.
+ ElementPtr map_element = Element::createMap();
+ for (unsigned i = 0; i < 20; ++i) {
+ std::ostringstream s;
+ s << "map_element" << i;
+ map_element->set(s.str(), list_element);
+ }
+
+ return (prettyPrint(map_element));
+ }
+
+ /// @brief Test that the JSONFeed correctly recognizes the beginning
+ /// and the end of the JSON structure.
+ ///
+ /// @param input_json A string holding an input JSON structure.
+ /// @param expected_output A structure holding expected output from the
+ /// @ref JSONFeed::toElement.
+ void testRead(const std::string& input_json,
+ const ConstElementPtr& expected_output) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+
+ // Post the data into the feed in 10 bytes long chunks.
+ size_t chunk = 10;
+
+ for (size_t i = 0; i < input_json.size(); i += chunk) {
+ bool done = false;
+ // When we're near the end of the data stream, the chunk length may
+ // vary.
+ if (i + chunk >= input_json.size()) {
+ chunk = input_json.size() - i;
+ done = true;
+ }
+ // Feed the parser with a data chunk and parse it.
+ feed.postBuffer(&input_json[i], chunk);
+ feed.poll();
+ if (!done) {
+ ASSERT_TRUE(feed.needData());
+ }
+ }
+
+ // Convert parsed/collected data in the feed into the structure of
+ // elements.
+ ConstElementPtr element_from_feed = feed.toElement();
+ EXPECT_TRUE(element_from_feed->equals(*expected_output));
+ }
+
+ /// @brief Test that the @ref JSONFeed signals an error when the input
+ /// string holds invalid data.
+ ///
+ /// @param input_json A string holding an input JSON structure.
+ void testInvalidRead(const std::string& input_json) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+
+ ASSERT_NO_THROW(feed.postBuffer(&input_json[0], input_json.size()));
+ ASSERT_NO_THROW(feed.poll());
+
+ EXPECT_FALSE(feed.needData());
+ EXPECT_FALSE(feed.feedOk());
+ }
+
+ /// @brief JSON map holding a number of lists.
+ ConstElementPtr json_map_;
+
+ /// @brief JSON list holding a map of lists.
+ ConstElementPtr json_list_;
+
+};
+
+// This test verifies that a JSON structure starting with '{' is accepted
+// and parsed.
+TEST_F(JSONFeedTest, startWithBrace) {
+ std::string json = createJSON();
+ testRead(json, json_map_);
+}
+
+// This test verifies that a JSON structure starting with '[' is accepted
+// and parsed.
+TEST_F(JSONFeedTest, startWithSquareBracket) {
+ std::string json = createJSON();
+ json = std::string("[") + json + std::string("]");
+ testRead(json, json_list_);
+}
+
+// This test verifies that input JSON can be preceded with whitespaces.
+TEST_F(JSONFeedTest, startWithWhitespace) {
+ std::string json = createJSON();
+ json = std::string(" \r\r\t ") + json;
+ testRead(json, json_map_);
+}
+
+// This test verifies that an empty map is accepted and parsed.
+TEST_F(JSONFeedTest, emptyMap) {
+ std::string json = "{}";
+ testRead(json, Element::createMap());
+}
+
+// This test verifies that an empty list is accepted and parsed.
+TEST_F(JSONFeedTest, emptyList) {
+ std::string json = "[ ]";
+ testRead(json, Element::createList());
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// is preceded by invalid character.
+TEST_F(JSONFeedTest, unexpectedCharacter) {
+ std::string json = "a {}";
+ testInvalidRead(json);
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// lacks an opening brace character.
+TEST_F(JSONFeedTest, noOpeningBrace) {
+ std::string json = "\"x\": \"y\" }";
+ testInvalidRead(json);
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// lacks an opening square bracket.
+TEST_F(JSONFeedTest, noOpeningSquareBracket) {
+ std::string json = "\"x\", \"y\" ]";
+ testInvalidRead(json);
+}
+
+} // end of anonymous namespace.
diff --git a/src/lib/cc/tests/simple_parser_unittest.cc b/src/lib/cc/tests/simple_parser_unittest.cc
new file mode 100644
index 0000000000..9febfe868e
--- /dev/null
+++ b/src/lib/cc/tests/simple_parser_unittest.cc
@@ -0,0 +1,237 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <stdint.h>
+#include <cc/simple_parser.h>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::asiolink;
+using isc::dhcp::DhcpConfigError;
+
+/// This table defines sample default values. Although these are DHCPv6
+/// specific, the mechanism is generic and can be used by any other component.
+const SimpleDefaults SAMPLE_DEFAULTS = {
+ { "renew-timer", Element::integer, "900" },
+ { "rebind-timer", Element::integer, "1800" },
+ { "preferred-lifetime", Element::integer, "3600" },
+ { "valid-lifetime", Element::integer, "7200" }
+};
+
+/// This list defines parameters that can be inherited from one scope
+/// to another. Although these are DHCPv6 specific, the mechanism is generic and
+/// can be used by any other component.
+const ParamsList SAMPLE_INHERITS = {
+ "renew-timer",
+ "rebind-timer",
+ "preferred-lifetime",
+ "valid-lifetime"
+};
+
+/// @brief Simple Parser test fixture class
+class SimpleParserTest : public ::testing::Test {
+public:
+ /// @brief Checks if specified map has an integer parameter with expected value
+ ///
+ /// @param map map to be checked
+ /// @param param_name name of the parameter to be checked
+ /// @param exp_value expected value of the parameter.
+ void checkIntegerValue(const ConstElementPtr& map, const std::string& param_name,
+ int64_t exp_value) {
+
+ // First check if the passed element is a map.
+ ASSERT_EQ(Element::map, map->getType());
+
+ // Now try to get the element being checked
+ ConstElementPtr elem = map->get(param_name);
+ ASSERT_TRUE(elem);
+
+ // Now check if it's indeed integer
+ ASSERT_EQ(Element::integer, elem->getType());
+
+ // Finally, check if its value meets expectation.
+ EXPECT_EQ(exp_value, elem->intValue());
+ }
+};
+
+class SimpleParserClassTest : public SimpleParser {
+public:
+ /// @brief Instantiation of getAndConvert
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter for error report
+ /// @return a bool value
+ bool getAsBool(ConstElementPtr scope, const std::string& name) {
+ return (getAndConvert<bool, toBool>(scope, name, "boolean"));
+ }
+
+ /// @brief Convert to boolean
+ ///
+ /// @param str the string "false" or "true"
+ /// @return false for "false" and true for "true"
+ /// @thrown isc::OutOfRange if not "false" or "true'
+ static bool toBool(const std::string& str) {
+ if (str == "false") {
+ return (false);
+ } else if (str == "true") {
+ return (true);
+ } else {
+ isc_throw(TypeError, "not a boolean: " << str);
+ }
+ }
+};
+
+// This test checks if the parameters can be inherited from the global
+// scope to the subnet scope.
+TEST_F(SimpleParserTest, deriveParams) {
+ ElementPtr global = Element::fromJSON("{ \"renew-timer\": 1,"
+ " \"rebind-timer\": 2,"
+ " \"preferred-lifetime\": 3,"
+ " \"valid-lifetime\": 4"
+ "}");
+ ElementPtr subnet = Element::fromJSON("{ \"renew-timer\": 100 }");
+
+ // we should inherit 3 parameters. Renew-timer should remain intact,
+ // as it was already defined in the subnet scope.
+ size_t num;
+ EXPECT_NO_THROW(num = SimpleParser::deriveParams(global, subnet,
+ SAMPLE_INHERITS));
+ EXPECT_EQ(3, num);
+
+ // Check the values. 3 of them are inherited, while the fourth one
+ // was already defined in the subnet, so should not be inherited.
+ checkIntegerValue(subnet, "renew-timer", 100);
+ checkIntegerValue(subnet, "rebind-timer", 2);
+ checkIntegerValue(subnet, "preferred-lifetime", 3);
+ checkIntegerValue(subnet, "valid-lifetime", 4);
+}
+
+// This test checks if global defaults are properly set for DHCPv6.
+TEST_F(SimpleParserTest, setDefaults) {
+
+ ElementPtr empty = Element::fromJSON("{ }");
+ size_t num = 0;
+
+ EXPECT_NO_THROW(num = SimpleParser::setDefaults(empty, SAMPLE_DEFAULTS));
+
+ // We expect at least 4 parameters to be inserted.
+ EXPECT_GE(num, 3);
+
+ checkIntegerValue(empty, "valid-lifetime", 7200);
+ checkIntegerValue(empty, "preferred-lifetime", 3600);
+ checkIntegerValue(empty, "rebind-timer", 1800);
+ checkIntegerValue(empty, "renew-timer", 900);
+}
+
+// This test checks if global defaults are properly set for DHCPv6.
+TEST_F(SimpleParserTest, setListDefaults) {
+
+ ElementPtr empty = Element::fromJSON("[{}, {}, {}]");
+ size_t num;
+
+ EXPECT_NO_THROW(num = SimpleParser::setListDefaults(empty, SAMPLE_DEFAULTS));
+
+ // We expect at least 12 parameters to be inserted (3 entries, with
+ // 4 parameters inserted in each)
+ EXPECT_EQ(12, num);
+
+ ASSERT_EQ(Element::list, empty->getType());
+ ASSERT_EQ(3, empty->size());
+
+ ConstElementPtr first = empty->get(0);
+ ConstElementPtr second = empty->get(1);
+ ConstElementPtr third = empty->get(2);
+
+ checkIntegerValue(first, "valid-lifetime", 7200);
+ checkIntegerValue(first, "preferred-lifetime", 3600);
+ checkIntegerValue(first, "rebind-timer", 1800);
+ checkIntegerValue(first, "renew-timer", 900);
+
+ checkIntegerValue(second, "valid-lifetime", 7200);
+ checkIntegerValue(second, "preferred-lifetime", 3600);
+ checkIntegerValue(second, "rebind-timer", 1800);
+ checkIntegerValue(second, "renew-timer", 900);
+
+ checkIntegerValue(third, "valid-lifetime", 7200);
+ checkIntegerValue(third, "preferred-lifetime", 3600);
+ checkIntegerValue(third, "rebind-timer", 1800);
+ checkIntegerValue(third, "renew-timer", 900);
+}
+
+// This test exercises the getIntType template
+TEST_F(SimpleParserTest, getIntType) {
+
+ SimpleParserClassTest parser;
+
+ // getIntType checks it can be found
+ ElementPtr not_found = Element::fromJSON("{ \"bar\": 1 }");
+ EXPECT_THROW(parser.getUint8(not_found, "foo"), DhcpConfigError);
+
+ // getIntType checks if it is an integer
+ ElementPtr not_int = Element::fromJSON("{ \"foo\": \"xyz\" }");
+ EXPECT_THROW(parser.getUint8(not_int, "foo"), DhcpConfigError);
+
+ // getIntType checks bounds
+ ElementPtr negative = Element::fromJSON("{ \"foo\": -1 }");
+ EXPECT_THROW(parser.getUint8(negative, "foo"), DhcpConfigError);
+ ElementPtr too_large = Element::fromJSON("{ \"foo\": 1024 }");
+ EXPECT_THROW(parser.getUint8(too_large, "foo"), DhcpConfigError);
+
+ // checks if getIntType can return the expected value
+ ElementPtr hundred = Element::fromJSON("{ \"foo\": 100 }");
+ uint8_t val = 0;
+ EXPECT_NO_THROW(val = parser.getUint8(hundred, "foo"));
+ EXPECT_EQ(100, val);
+}
+
+// This test exercises the getAndConvert template
+TEST_F(SimpleParserTest, getAndConvert) {
+
+ SimpleParserClassTest parser;
+
+ // getAndConvert checks it can be found
+ ElementPtr not_found = Element::fromJSON("{ \"bar\": \"true\" }");
+ EXPECT_THROW(parser.getAsBool(not_found, "foo"), DhcpConfigError);
+
+ // getAndConvert checks if it is a string
+ ElementPtr not_bool = Element::fromJSON("{ \"foo\": 1 }");
+ EXPECT_THROW(parser.getAsBool(not_bool, "foo"), DhcpConfigError);
+
+ // checks if getAndConvert can return the expected value
+ ElementPtr a_bool = Element::fromJSON("{ \"foo\": \"false\" }");
+ bool val = true;
+ EXPECT_NO_THROW(val = parser.getAsBool(a_bool, "foo"));
+ EXPECT_FALSE(val);
+
+ // getAndConvert checks conversion
+ ElementPtr bad_bool = Element::fromJSON("{ \"foo\": \"bar\" }");
+ EXPECT_THROW(parser.getAsBool(bad_bool, "bar"), DhcpConfigError);
+}
+
+// This test exercises the getIOAddress
+TEST_F(SimpleParserTest, getIOAddress) {
+
+ SimpleParserClassTest parser;
+
+ // getAddress checks it can be found
+ ElementPtr not_found = Element::fromJSON("{ \"bar\": 1 }");
+ EXPECT_THROW(parser.getAddress(not_found, "foo"), DhcpConfigError);
+
+ // getAddress checks if it is a string
+ ElementPtr not_addr = Element::fromJSON("{ \"foo\": 1234 }");
+ EXPECT_THROW(parser.getAddress(not_addr, "foo"), DhcpConfigError);
+
+ // checks if getAddress can return the expected value of v4 address
+ ElementPtr v4 = Element::fromJSON("{ \"foo\": \"192.0.2.1\" }");
+ IOAddress val("::");
+ EXPECT_NO_THROW(val = parser.getAddress(v4, "foo"));
+ EXPECT_EQ("192.0.2.1" , val.toText());
+
+ // checks if getAddress can return the expected value of v4 address
+ ElementPtr v6 = Element::fromJSON("{ \"foo\": \"2001:db8::1\" }");
+ EXPECT_NO_THROW(val = parser.getAddress(v6, "foo"));
+ EXPECT_EQ("2001:db8::1" , val.toText());
+}
diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am
index 6025276d59..c1487d59f7 100644
--- a/src/lib/config/Makefile.am
+++ b/src/lib/config/Makefile.am
@@ -15,23 +15,25 @@ BUILT_SOURCES = config_messages.h config_messages.cc
lib_LTLIBRARIES = libkea-cfgclient.la
libkea_cfgclient_la_SOURCES = config_data.h config_data.cc
libkea_cfgclient_la_SOURCES += module_spec.h module_spec.cc
+libkea_cfgclient_la_SOURCES += base_command_mgr.cc base_command_mgr.h
+libkea_cfgclient_la_SOURCES += client_connection.cc client_connection.h
libkea_cfgclient_la_SOURCES += command_mgr.cc command_mgr.h
-libkea_cfgclient_la_SOURCES += command_socket.cc command_socket.h
-libkea_cfgclient_la_SOURCES += command_socket_factory.cc command_socket_factory.h
libkea_cfgclient_la_SOURCES += config_log.h config_log.cc
+libkea_cfgclient_la_SOURCES += hooked_command_mgr.cc hooked_command_mgr.h
libkea_cfgclient_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
libkea_cfgclient_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
-libkea_cfgclient_la_LDFLAGS = -no-undefined -version-info 2:1:0
+libkea_cfgclient_la_LDFLAGS = -no-undefined -version-info 3:0:0
libkea_cfgclient_la_LDFLAGS += $(CRYPTO_LDFLAGS)
nodist_libkea_cfgclient_la_SOURCES = config_messages.h config_messages.cc
diff --git a/src/lib/config/base_command_mgr.cc b/src/lib/config/base_command_mgr.cc
new file mode 100644
index 0000000000..88b6a290d0
--- /dev/null
+++ b/src/lib/config/base_command_mgr.cc
@@ -0,0 +1,143 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <cc/command_interpreter.h>
+#include <config/base_command_mgr.h>
+#include <config/config_log.h>
+#include <boost/bind.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace config {
+
+BaseCommandMgr::BaseCommandMgr() {
+ registerCommand("list-commands", boost::bind(&BaseCommandMgr::listCommandsHandler,
+ this, _1, _2));
+}
+
+void
+BaseCommandMgr::registerCommand(const std::string& cmd, CommandHandler handler) {
+ if (!handler) {
+ isc_throw(InvalidCommandHandler, "Specified command handler is NULL");
+ }
+
+ HandlerContainer::const_iterator it = handlers_.find(cmd);
+ if (it != handlers_.end()) {
+ isc_throw(InvalidCommandName, "Handler for command '" << cmd
+ << "' is already installed.");
+ }
+
+ HandlersPair handlers;
+ handlers.handler = handler;
+ handlers_.insert(make_pair(cmd, handlers));
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_REGISTERED).arg(cmd);
+}
+
+void
+BaseCommandMgr::registerExtendedCommand(const std::string& cmd,
+ ExtendedCommandHandler handler) {
+ if (!handler) {
+ isc_throw(InvalidCommandHandler, "Specified command handler is NULL");
+ }
+
+ HandlerContainer::const_iterator it = handlers_.find(cmd);
+ if (it != handlers_.end()) {
+ isc_throw(InvalidCommandName, "Handler for command '" << cmd
+ << "' is already installed.");
+ }
+
+ HandlersPair handlers;
+ handlers.extended_handler = handler;
+ handlers_.insert(make_pair(cmd, handlers));
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_EXTENDED_REGISTERED).arg(cmd);
+}
+
+void
+BaseCommandMgr::deregisterCommand(const std::string& cmd) {
+ if (cmd == "list-commands") {
+ isc_throw(InvalidCommandName,
+ "Can't uninstall internal command 'list-commands'");
+ }
+
+ HandlerContainer::iterator it = handlers_.find(cmd);
+ if (it == handlers_.end()) {
+ isc_throw(InvalidCommandName, "Handler for command '" << cmd
+ << "' not found.");
+ }
+ handlers_.erase(it);
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_DEREGISTERED).arg(cmd);
+}
+
+void
+BaseCommandMgr::deregisterAll() {
+
+ // No need to log anything here. deregisterAll is not used in production
+ // code, just in tests.
+ handlers_.clear();
+ registerCommand("list-commands",
+ boost::bind(&BaseCommandMgr::listCommandsHandler, this, _1, _2));
+}
+
+isc::data::ConstElementPtr
+BaseCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
+ if (!cmd) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "Command processing failed: NULL command parameter"));
+ }
+
+ try {
+ ConstElementPtr arg;
+ std::string name = parseCommand(arg, cmd);
+
+ LOG_INFO(command_logger, COMMAND_RECEIVED).arg(name);
+
+ return (handleCommand(name, arg, cmd));
+
+ } catch (const Exception& e) {
+ LOG_WARN(command_logger, COMMAND_PROCESS_ERROR2).arg(e.what());
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ std::string("Error during command processing: ")
+ + e.what()));
+ }
+}
+
+ConstElementPtr
+BaseCommandMgr::handleCommand(const std::string& cmd_name,
+ const ConstElementPtr& params,
+ const ConstElementPtr& original_cmd) {
+ auto it = handlers_.find(cmd_name);
+ if (it == handlers_.end()) {
+ // Ok, there's no such command.
+ return (createAnswer(CONTROL_RESULT_COMMAND_UNSUPPORTED,
+ "'" + cmd_name + "' command not supported."));
+ }
+
+ // Call the actual handler and return whatever it returned
+ if (it->second.handler) {
+ return (it->second.handler(cmd_name, params));
+ }
+ return (it->second.extended_handler(cmd_name, params, original_cmd));
+}
+
+isc::data::ConstElementPtr
+BaseCommandMgr::listCommandsHandler(const std::string& name,
+ const isc::data::ConstElementPtr& ) {
+ using namespace isc::data;
+ ElementPtr commands = Element::createList();
+ for (HandlerContainer::const_iterator it = handlers_.begin();
+ it != handlers_.end(); ++it) {
+ commands->add(Element::create(it->first));
+ }
+ return (createAnswer(CONTROL_RESULT_SUCCESS, commands));
+}
+
+
+} // namespace isc::config
+} // namespace isc
diff --git a/src/lib/config/base_command_mgr.h b/src/lib/config/base_command_mgr.h
new file mode 100644
index 0000000000..19bbae3248
--- /dev/null
+++ b/src/lib/config/base_command_mgr.h
@@ -0,0 +1,199 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef BASE_COMMAND_MGR_H
+#define BASE_COMMAND_MGR_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <boost/function.hpp>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace config {
+
+/// @brief Exception indicating that the handler specified is not valid
+class InvalidCommandHandler : public Exception {
+public:
+ InvalidCommandHandler(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception indicating that the command name is not valid
+class InvalidCommandName : public Exception {
+public:
+ InvalidCommandName(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Commands Manager, responsible for processing external commands.
+///
+/// Commands Manager is a generic interface for handling external commands.
+/// Commands are received over control sockets. Derivations of this class
+/// provide implementations of the control socket layers, e.g. unix domain
+/// sockets, TCP sockets etc. This base class merely provides methods to manage
+/// command handling functions, i.e. register commands, deregister commands.
+/// It also includes a @ref BaseCommandMgr::processCommand method which
+/// uses the command as an input and invokes appropriate handlers.
+///
+/// The commands and responses are formatted using JSON.
+/// See http://kea.isc.org/wiki/StatsDesign for details.
+///
+/// Below is an example of the command using JSON format:
+/// @code
+/// {
+/// "command": "statistic-get",
+/// "arguments": {
+/// "name": "received-packets"
+/// }
+/// }
+/// @endcode
+///
+/// And the response is:
+///
+/// @code
+/// {
+/// "result": 0,
+/// "arguments": {
+/// "received-packets": [ [ 1234, "2015-04-15 12:34:45.123" ] ]
+/// }
+/// }
+/// @endcode
+///
+/// BaseCommandsMgr does not implement the commands (except one,
+/// "list-commands") itself, but rather provides an interface
+/// (see @ref registerCommand, @ref deregisterCommand, @ref processCommand)
+/// for other components to use it.
+class BaseCommandMgr {
+public:
+
+ /// @brief Defines command handler type
+ ///
+ /// Command handlers are expected to use this format.
+ ///
+ /// @param name name of the commands
+ /// @param params parameters specific to the command
+ /// @return response (created with createAnswer())
+ typedef boost::function<isc::data::ConstElementPtr (const std::string& name,
+ const isc::data::ConstElementPtr& params)> CommandHandler;
+
+ /// @brief Defines extended command handler type.
+ ///
+ /// This command handler includes third parameter which holds the
+ /// entire command control message. The handler can retrieve
+ /// additional information from this parameter, e.g. 'service'.
+ ///
+ /// @param name name of the commands
+ /// @param params parameters specific to the command
+ /// @param original original control command.
+ /// @return response (created with createAnswer())
+ typedef boost::function<isc::data::ConstElementPtr (const std::string& name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original)> ExtendedCommandHandler;
+
+ /// @brief Constructor.
+ ///
+ /// Registers "list-commands" command.
+ BaseCommandMgr();
+
+ /// @brief Destructor.
+ virtual ~BaseCommandMgr() { };
+
+ /// @brief Triggers command processing.
+ ///
+ /// This method processes specified command. The command is specified using
+ /// a single Element. See @ref BaseCommandMgr for description of its syntax.
+ ///
+ /// @param cmd Pointer to the data element representing command in JSON
+ /// format.
+ isc::data::ConstElementPtr
+ processCommand(const isc::data::ConstElementPtr& cmd);
+
+ /// @brief Registers specified command handler for a given command
+ ///
+ /// @param cmd Name of the command to be handled.
+ /// @param handler Pointer to the method that will handle the command.
+ void registerCommand(const std::string& cmd, CommandHandler handler);
+
+ /// @brief Registers specified command handler for a given command.
+ ///
+ /// This variant of the method uses extended command handler which, besides
+ /// command name and arguments, also has a third parameter 'original_cmd'
+ /// in its signature. Such handlers can retrieve additional parameters from
+ /// the command, e.g. 'service' indicating where the command should be
+ /// routed.
+ ///
+ /// @param cmd Name of the command to be handled.
+ /// @param handler Pointer to the method that will handle the command.
+ void registerExtendedCommand(const std::string& cmd,
+ ExtendedCommandHandler handler);
+
+ /// @brief Deregisters specified command handler.
+ ///
+ /// @param cmd Name of the command that's no longer handled.
+ void deregisterCommand(const std::string& cmd);
+
+ /// @brief Auxiliary method that removes all installed commands.
+ ///
+ /// The only unwipeable method is list-commands, which is internally
+ /// handled at all times.
+ void deregisterAll();
+
+protected:
+
+ /// @brief Handles the command having a given name and arguments.
+ ///
+ /// This method can be overridden in the derived classes to provide
+ /// custom logic for processing commands. For example, the
+ /// @ref HookedCommandMgr extends this method to delegate commands
+ /// processing to a hook library.
+ ///
+ /// @param cmd_name Command name.
+ /// @param params Command arguments.
+ /// @param original_cmd Pointer to the entire command received. It may
+ /// be sometimes useful to retrieve additional parameters from this
+ /// command.
+ ///
+ /// @return Pointer to the const data element representing response
+ /// to a command.
+ virtual isc::data::ConstElementPtr
+ handleCommand(const std::string& cmd_name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original_cmd);
+
+ struct HandlersPair {
+ CommandHandler handler;
+ ExtendedCommandHandler extended_handler;
+ };
+
+ /// @brief Type of the container for command handlers.
+ typedef std::map<std::string, HandlersPair> HandlerContainer;
+
+ /// @brief Container for command handlers.
+ HandlerContainer handlers_;
+
+private:
+
+ /// @brief 'list-commands' command handler.
+ ///
+ /// This method implements command 'list-commands'. It returns a list of all
+ /// currently supported commands.
+ ///
+ /// @param name Name of the command (should always be 'list-commands').
+ /// @param params Additional parameters (ignored).
+ ///
+ /// @return Pointer to the structure that includes all currently supported
+ /// commands.
+ isc::data::ConstElementPtr
+ listCommandsHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+};
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/client_connection.cc b/src/lib/config/client_connection.cc
new file mode 100644
index 0000000000..2045d263e4
--- /dev/null
+++ b/src/lib/config/client_connection.cc
@@ -0,0 +1,258 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/unix_domain_socket.h>
+#include <cc/json_feed.h>
+#include <config/client_connection.h>
+#include <boost/bind.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <array>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace config {
+
+/// @brief Implementation of the @ref ClientConnection.
+class ClientConnectionImpl : public boost::enable_shared_from_this<ClientConnectionImpl> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ explicit ClientConnectionImpl(IOService& io_service);
+
+ /// @brief Starts asynchronous transaction with a remote endpoint.
+ ///
+ /// See @ref ClientConnection::start documentation for the details.
+ ///
+ /// @param socket_path Path to the socket description that the server
+ /// is bound to.
+ /// @param command Control command to be sent to the server.
+ /// @param handler Pointer to the user supplied callback function which
+ /// should be invoked when transaction completes or when an error has
+ /// occurred during the transaction.
+ /// @param timeout Connection timeout in milliseconds.
+ void start(const ClientConnection::SocketPath& socket_path,
+ const ClientConnection::ControlCommand& command,
+ ClientConnection::Handler handler,
+ const ClientConnection::Timeout& timeout);
+
+ /// @brief Closes the socket.
+ void stop();
+
+ /// @brief Starts asynchronous send.
+ ///
+ /// This method may be called multiple times internally when the command
+ /// is large and can't be sent all at once.
+ ///
+ /// @param buffer Pointer to the buffer holding input data.
+ /// @param length Length of the data in the input buffer.
+ /// @param handler User supplied callback invoked after the chunk of data
+ /// has been sent.
+ void doSend(const void* buffer, const size_t length,
+ ClientConnection::Handler handler);
+
+ /// @brief Starts asynchronous receive from the server.
+ ///
+ /// This method may be called multiple times internally if the response
+ /// is large. The @ref JSONFeed instance is used to detect the boundaries
+ /// of the command within the stream. Once the entire command has been
+ /// received the user callback is invoked and the instance of the
+ /// @ref JSONFeed is returned.
+ ///
+ /// @param handler User supplied callback.
+ void doReceive(ClientConnection::Handler handler);
+
+ /// @brief Terminates the connection and invokes a user callback indicating
+ /// an error.
+ ///
+ /// @param ec Error code.
+ /// @param handler User callback.
+ void terminate(const boost::system::error_code& ec,
+ ClientConnection::Handler handler);
+
+ /// @brief Callback invoked when the timeout occurs.
+ ///
+ /// It calls @ref terminate with the @c boost::asio::error::timed_out.
+ void timeoutCallback(ClientConnection::Handler handler);
+
+private:
+
+ /// @brief Unix domain socket used for communication with a server.
+ UnixDomainSocket socket_;
+
+ /// @brief Pointer to the @ref JSONFeed holding a response.
+ ///
+ ///It may be a null pointer until some part of a response has been received.
+ JSONFeedPtr feed_;
+
+ /// @brief Holds the entire command being transmitted over the unix
+ /// socket.
+ std::string current_command_;
+
+ /// @brief Buffer into which chunks of the response are received.
+ std::array<char, 1024> read_buf_;
+
+ /// @brief Instance of the interval timer protecting against timeouts.
+ IntervalTimer timer_;
+};
+
+ClientConnectionImpl::ClientConnectionImpl(IOService& io_service)
+ : socket_(io_service), feed_(), current_command_(), timer_(io_service) {
+}
+
+void
+ClientConnectionImpl::start(const ClientConnection::SocketPath& socket_path,
+ const ClientConnection::ControlCommand& command,
+ ClientConnection::Handler handler,
+ const ClientConnection::Timeout& timeout) {
+ // Start the timer protecting against timeouts.
+ timer_.setup(boost::bind(&ClientConnectionImpl::timeoutCallback,
+ this, handler),
+ timeout.timeout_, IntervalTimer::ONE_SHOT);
+
+ // Store the command in the class member to make sure it is valid
+ // the entire time.
+ current_command_.assign(command.control_command_);
+
+ // Pass self to lambda to make sure that the instance of this class
+ // lives as long as the lambda is held for async connect.
+ auto self(shared_from_this());
+ // Start asynchronous connect. This will return immediately.
+ socket_.asyncConnect(socket_path.socket_path_,
+ [this, self, command, handler](const boost::system::error_code& ec) {
+ // We failed to connect so we can't proceed. Simply clean up
+ // and invoke the user callback to signal an error.
+ if (ec) {
+ // This doesn't throw.
+ terminate(ec, handler);
+
+ } else {
+ // Connection successful. Transmit the command to the remote
+ // endpoint asynchronously.
+ doSend(current_command_.c_str(), current_command_.length(),
+ handler);
+ }
+ });
+}
+
+void
+ClientConnectionImpl::doSend(const void* buffer, const size_t length,
+ ClientConnection::Handler handler) {
+ // Pass self to lambda to make sure that the instance of this class
+ // lives as long as the lambda is held for async send.
+ auto self(shared_from_this());
+ // Start asynchronous transmission of the command. This will return
+ // immediately.
+ socket_.asyncSend(buffer, length,
+ [this, self, buffer, length, handler]
+ (const boost::system::error_code& ec, size_t bytes_transferred) {
+ // An error has occurred while sending. Close the connection and
+ // signal an error.
+ if (ec) {
+ // This doesn't throw.
+ terminate(ec, handler);
+
+ } else {
+ // If the number of bytes we have managed to send so far is
+ // lower than the amount of data we're trying to send, we
+ // have to schedule another send to deliver the rest of
+ // the data.
+ if (bytes_transferred < length) {
+ doSend(static_cast<const char*>(buffer) + bytes_transferred,
+ length - bytes_transferred, handler);
+
+ } else {
+ // We have sent all the data. Start receiving a response.
+ doReceive(handler);
+ }
+ }
+ });
+}
+
+void
+ClientConnectionImpl::doReceive(ClientConnection::Handler handler) {
+ // Pass self to lambda to make sure that the instance of this class
+ // lives as long as the lambda is held for async receive.
+ auto self(shared_from_this());
+ socket_.asyncReceive(&read_buf_[0], read_buf_.size(),
+ [this, self, handler]
+ (const boost::system::error_code& ec, size_t length) {
+ // An error has occurred while receiving the data. Close the connection
+ // and signal an error.
+ if (ec) {
+ // This doesn't throw.
+ terminate(ec, handler);
+
+ } else {
+ std::string x(&read_buf_[0], length);
+ // Lazy initialization of the JSONFeed. The feed will be "parsing"
+ // received JSON stream and will detect when the whole response
+ // has been received.
+ if (!feed_) {
+ feed_.reset(new JSONFeed());
+ feed_->initModel();
+ }
+ // Put everything we have received so far into the feed and process
+ // the data.
+ feed_->postBuffer(&read_buf_[0], length);
+ feed_->poll();
+ // If the feed indicates that only a part of the response has been
+ // received, schedule another receive to get more data.
+ if (feed_->needData()) {
+ doReceive(handler);
+
+ } else {
+ // We have received the entire response, let's call the handler
+ // and indicate success.
+ terminate(ec, handler);
+ }
+ }
+ });
+}
+
+void
+ClientConnectionImpl::terminate(const boost::system::error_code& ec,
+ ClientConnection::Handler handler) {
+ try {
+ timer_.cancel();
+ socket_.close();
+ current_command_.clear();
+ handler(ec, feed_);
+
+ } catch (...) {
+ // None of these operations should throw. In particular, the handler
+ // should not throw but if it has been misimplemented, we want to make
+ // sure we don't emit any exceptions from here.
+ }
+}
+
+void
+ClientConnectionImpl::timeoutCallback(ClientConnection::Handler handler) {
+ // Timeout has occurred. The remote server didn't provide the entire
+ // response within the given time frame. Let's close the connection
+ // and signal the timeout.
+ terminate(boost::asio::error::timed_out, handler);
+}
+
+ClientConnection::ClientConnection(asiolink::IOService& io_service)
+ : impl_(new ClientConnectionImpl(io_service)) {
+}
+
+void
+ClientConnection::start(const ClientConnection::SocketPath& socket_path,
+ const ClientConnection::ControlCommand& command,
+ ClientConnection::Handler handler,
+ const ClientConnection::Timeout& timeout) {
+ impl_->start(socket_path, command, handler, timeout);
+}
+
+
+} // end of namespace config
+} // end of namespace isc
diff --git a/src/lib/config/client_connection.h b/src/lib/config/client_connection.h
new file mode 100644
index 0000000000..e2b8f805ea
--- /dev/null
+++ b/src/lib/config/client_connection.h
@@ -0,0 +1,157 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef CLIENT_CONNECTION_H
+#define CLIENT_CONNECTION_H
+
+#include <asiolink/io_service.h>
+#include <cc/json_feed.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+
+namespace isc {
+namespace config {
+
+class ClientConnectionImpl;
+
+/// @brief Represents client side connection over the unix domain socket.
+///
+/// This class represents a client side connection between the controlling
+/// client and the server exposing control API over a unix domain socket.
+/// In particular, this class is used by the Kea Control Agent to establish
+/// connections with respective Kea services to forward received commands.
+/// As of Kea 1.2 the servers can handle a single connection at the time.
+/// In the future, we're planning to support multiple simulatenous connections.
+/// In this case, each connection will be handled by a unique instance of the
+/// @ref ClientConnection class.
+///
+/// The @ref ClientConnection supports asynchronous connections. A caller
+/// creates an instance of the @ref ClientConnection and calls
+/// @ref ClientConnection::start to start asynchronous communication with
+/// a remote server. The caller provides a pointer to the callback function
+/// (handler) which will be called when the communication with the server
+/// completes, i.e. the command is sent to the server and the response
+/// from the server is received. If an error occurs, the callback is
+/// invoked with an error code indicating a reason for the failure.
+///
+/// The documentation of the @ref ClientConnection::start explains the
+/// sequence of operations performed by this class.
+///
+/// Even though the @ref ClientConnection is asynchronous in nature, it
+/// can also be used in cases requiring synchronous communication. As it
+/// has been already mentioned, the servers in Kea 1.2 do not support
+/// multiple concurrent connections. The following pseudo code demonstrates
+/// how to perform synchronous transaction using this class.
+///
+/// @code
+/// IOService io_service;
+/// ClientConnection conn(io_service);
+/// bool cb_invoked = false;
+/// conn.start(ClientConnection::SocketPath("/tmp/kea.sock"),
+/// ClientConnection::ControlCommand(command),
+/// [this, &cb_invoked](const boost::system::error_code& ec,
+/// const ConstJSONFeedPtr& feed) {
+/// cb_invoked = true;
+/// if (ec) {
+/// ... handle error here ...
+/// } else {
+/// ... use feed to retrieve the response ...
+/// }
+/// }
+/// );
+/// while (!cb_invoked) {
+/// io_service.run_one();
+/// }
+/// @endcode
+///
+class ClientConnection {
+public:
+
+ /// @name Structures used for strong typing.
+ ///
+ //@{
+
+ /// @brief Encapsulates socket path.
+ struct SocketPath {
+ explicit SocketPath(const std::string& socket_path)
+ : socket_path_(socket_path) { }
+
+ std::string socket_path_;
+ };
+
+ /// @brief Encapsulates control command.
+ struct ControlCommand {
+ explicit ControlCommand(const std::string control_command)
+ : control_command_(control_command) { }
+
+ std::string control_command_;
+ };
+
+ /// @brief Encapsulates timeout value.
+ struct Timeout {
+ explicit Timeout(const long timeout)
+ : timeout_(timeout) { }
+
+ long timeout_;
+ };
+
+ //@}
+
+ /// @brief Type of the callback invoked when the communication with
+ /// the server is complete or an error has occurred.
+ typedef std::function<void(const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& feed)> Handler;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ explicit ClientConnection(asiolink::IOService& io_service);
+
+ /// @brief Starts asynchronous transaction with a remote endpoint.
+ ///
+ /// Starts asynchronous connection with the remote endpoint. If the
+ /// connection is successful, the control command is asynchronously
+ /// sent to the remote endpoint. When the entire command has been sent,
+ /// the response is read asynchronously, possibly in multiple chunks.
+ ///
+ /// The timeout is specified for the entire transaction in milliseconds.
+ /// If the transaction takes longer than the timeout value the connection
+ /// is closed and the callback is called with the error code of
+ /// @c boost::asio::error::timed_out.
+ ///
+ /// In other cases, the callback is called with the error code returned
+ /// by the boost asynchronous operations. If the transaction is successful
+ /// the 'success' status is indicated with the error code. In addition
+ /// the instance of the @ref JSONFeed is returned to the caller. It can
+ /// be used to retrieve parsed response from the server. Note that the
+ /// response may still be malformed, even if no error is signalled in
+ /// the handler. The @ref JSONFeed::toElement will return a parsing
+ /// error if the JSON appears to be malformed.
+ ///
+ /// @param socket_path Path to the socket description that the server
+ /// is bound to.
+ /// @param command Control command to be sent to the server.
+ /// @param handler Pointer to the user supplied callback function which
+ /// should be invoked when transaction completes or when an error has
+ /// occurred during the transaction.
+ /// @param timeout Connection timeout in milliseconds.
+ void start(const SocketPath& socket_path, const ControlCommand& command,
+ Handler handler, const Timeout& timeout = Timeout(5000));
+
+private:
+
+ /// @brief Pointer to the implementation.
+ boost::shared_ptr<ClientConnectionImpl> impl_;
+
+};
+
+/// @brief Type of the pointer to the @ref ClientConnection object.
+typedef boost::shared_ptr<ClientConnection> ClientConnectionPtr;
+
+} // end of namespace config
+} // end of namespace isc
+
+#endif // CLIENT_CONNECTION_H
diff --git a/src/lib/config/command-socket.dox b/src/lib/config/command-socket.dox
index d8311d3dd0..bbe94e83c9 100644
--- a/src/lib/config/command-socket.dox
+++ b/src/lib/config/command-socket.dox
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,7 +13,7 @@ In many cases it is useful to manage certain aspects of the DHCP servers
while they are running. In Kea, this may be done via the Control Channel.
Control Channel allows an external entity (e.g. a tool run by a sysadmin
or a script) to issue commands to the server which can influence its
-behavior or retreive information from it. Several notable examples
+behavior or retrieve information from it. Several notable examples
envisioned are: reconfiguration, statistics retrieval and manipulation,
and shutdown.
@@ -134,7 +134,7 @@ int main(int argc, const char* argv[]) {
@section ctrlSocketImpl Control Channel Implementation
-Control Channel is implemented in @ref isc::config::CommandMgr. It is a signleton
+Control Channel is implemented in @ref isc::config::CommandMgr. It is a singleton
class that allows registration of callbacks that handle specific commands.
It internally supports a single command: @c list-commands that returns a list
of supported commands. This component is expected to be shared among all daemons.
@@ -154,39 +154,21 @@ or HTTPS connection):
- @ref isc::config::CommandMgr::openCommandSocket that passes structure defined
in the configuration file. Currently only two parameters are supported: socket-type
(which must contain value 'unix') and socket-name (which contains unix path for
- the named socket to be created). This method calls @ref
- isc::config::CommandSocketFactory::create method, which parses the parameters
- and instantiates one object from a class derived from @ref isc::config::CommandSocket.
- Again, currently only UNIX type is supported, but the factory
- class is expected to be extended to cover additional types.
+ the named socket to be created).
- @ref isc::config::CommandMgr::closeCommandSocket() - it is used to close the
- socket. It calls close method on the @ref isc::config::CommandSocket object, if
- one exists. In particular, for UNIX socket, it also deletes the file after socket
- was closed.
+ socket.
@section ctrlSocketConnections Accepting connections
-Control Channel is connection oriented communication. In that sense it is
-different than all other communications supported so far in Kea. To facilitate
-connections, several mechanisms were implemented. Intially a single UNIX socket
-it opened (see @c isc::config::UnixCommandSocket in
-src/lib/config/command_socket_factory.cc). Its @ref
-isc::config::UnixCommandSocket::receiveHandler callback method is
-installed in @ref isc::dhcp::IfaceMgr to process incoming connections. When the
-select call in @ref isc::dhcp::IfaceMgr::receive4 indicates that there is some data to be
-processed, this callback calls accept, which creates a new socket for handling
-this particular incoming connection. Once the socket descriptor is known, a new
-instance of @ref isc::config::ConnectionSocket is created to represent that
-socket (and the whole ongoing connection). It installs another callback
-(@ref isc::config::ConnectionSocket::receiveHandler that calls
-(@ref isc::config::CommandMgr::commandReader) that will process incoming
-data or will close the socket when necessary. CommandReader reads data from
-incoming socket and attempts to parse it as JSON structures. If successful,
-it calls isc::config::CommandMgr::processCommand(), serializes the structure
-returned and attempts to send it back.
-
-@todo Currently commands and responses up to 64KB are supported. It was deemed
-sufficient for the current needs, but in the future we may need to extend
-it to handle bigger structures.
+The @ref isc::config::CommandMgr is implemented using boost ASIO and uses
+asynchronous calls to accept new connections and receive commands from the
+controlling clients. ASIO uses IO service object to run asynchronous calls.
+Thus, before the server can use the @ref isc::config::CommandMgr it must
+provide it with a common instance of the @ref isc::asiolink::IOService
+object using @ref isc::config::CommandMgr::setIOService. The server's
+main loop must contain calls to @ref isc::asiolink::IOService::run or
+@ref isc::asiolink::IOService::poll or their variants to invoke Command
+Manager's handlers as required for processing control requests.
+
*/
diff --git a/src/lib/config/command_mgr.cc b/src/lib/config/command_mgr.cc
index ae33188014..bd1c04b1cb 100644
--- a/src/lib/config/command_mgr.cc
+++ b/src/lib/config/command_mgr.cc
@@ -1,238 +1,561 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <asiolink/unix_domain_socket.h>
+#include <asiolink/unix_domain_socket_acceptor.h>
+#include <asiolink/unix_domain_socket_endpoint.h>
#include <config/command_mgr.h>
-#include <config/command_socket_factory.h>
#include <cc/data.h>
#include <cc/command_interpreter.h>
+#include <cc/json_feed.h>
#include <dhcp/iface_mgr.h>
#include <config/config_log.h>
#include <boost/bind.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <array>
+#include <unistd.h>
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
using namespace isc::data;
-namespace isc {
-namespace config {
+namespace {
+
+/// @brief Maximum size of the data chunk sent/received over the socket.
+const size_t BUF_SIZE = 8192;
+
+/// @brief Default connection timeout in seconds.
+const unsigned short DEFAULT_CONNECTION_TIMEOUT = 10;
+
+class ConnectionPool;
+
+/// @brief Represents a single connection over control socket.
+///
+/// An instance of this object is created when the @c CommandMgr acceptor
+/// receives new connection from a controlling client.
+class Connection : public boost::enable_shared_from_this<Connection> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor registers a socket of this connection in the Interface
+ /// Manager to cause the blocking call to @c select() to return as soon as
+ /// a transmission over the control socket is received.
+ ///
+ /// @param io_service IOService object used to handle the asio operations
+ /// @param socket Pointer to the object representing a socket which is used
+ /// for data transmission.
+ /// @param connection_pool Reference to the connection pool to which this
+ /// connection belongs.
+ /// @param timeout Connection timeout (in seconds).
+ Connection(const IOServicePtr& io_service,
+ const boost::shared_ptr<UnixDomainSocket>& socket,
+ ConnectionPool& connection_pool,
+ const unsigned short timeout)
+ : socket_(socket), timeout_timer_(*io_service), timeout_(timeout),
+ buf_(), response_(), connection_pool_(connection_pool), feed_(),
+ response_in_progress_(false) {
+
+ LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_OPENED)
+ .arg(socket_->getNative());
+
+ // Callback value of 0 is used to indicate that callback function is
+ // not installed.
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(socket_->getNative(), 0);
+ // Initialize state model for receiving and preparsing commands.
+ feed_.initModel();
+
+ // Start timer for detecting timeouts.
+ timeout_timer_.setup(boost::bind(&Connection::timeoutHandler, this),
+ timeout_ * 1000, IntervalTimer::ONE_SHOT);
+ }
-CommandMgr::CommandMgr() {
- registerCommand("list-commands",
- boost::bind(&CommandMgr::listCommandsHandler, this, _1, _2));
-}
+ /// @brief Destructor.
+ ///
+ /// Cancels timeout timer if one is scheduled.
+ ~Connection() {
+ timeout_timer_.cancel();
+ }
-CommandSocketPtr
-CommandMgr::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
- if (socket_) {
- isc_throw(SocketError, "There is already a control socket open");
+ /// @brief Close current connection.
+ ///
+ /// Connection is not closed if the invocation of this method is a result of
+ /// server reconfiguration. The connection will be closed once a response is
+ /// sent to the client. Closing a socket during processing a request would
+ /// cause the server to not send a response to the client.
+ void stop() {
+ if (!response_in_progress_) {
+ LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_CLOSED)
+ .arg(socket_->getNative());
+
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(socket_->getNative());
+ socket_->close();
+ timeout_timer_.cancel();
+ }
}
- socket_ = CommandSocketFactory::create(socket_info);
+ /// @brief Gracefully terminates current connection.
+ ///
+ /// This method should be called prior to closing the socket to initiate
+ /// graceful shutdown.
+ void terminate();
- return (socket_);
-}
+ /// @brief Start asynchronous read over the unix domain socket.
+ ///
+ /// This method doesn't block. Once the transmission is received over the
+ /// socket, the @c Connection::receiveHandler callback is invoked to
+ /// process received data.
+ void doReceive() {
+ socket_->asyncReceive(&buf_[0], sizeof(buf_),
+ boost::bind(&Connection::receiveHandler,
+ shared_from_this(), _1, _2));
-void CommandMgr::closeCommandSocket() {
- // First, let's close the socket for incoming new connections.
- if (socket_) {
- socket_->close();
- socket_.reset();
- }
- // Now let's close all existing connections that we may have.
- for (std::list<CommandSocketPtr>::iterator conn = connections_.begin();
- conn != connections_.end(); ++conn) {
- (*conn)->close();
}
- connections_.clear();
-}
-
-void CommandMgr::addConnection(const CommandSocketPtr& conn) {
- connections_.push_back(conn);
-}
+ /// @brief Starts asynchronous send over the unix domain socket.
+ ///
+ /// This method doesn't block. Once the send operation (that covers the whole
+ /// data if it's small or first BUF_SIZE bytes if its large) is completed, the
+ /// @c Connection::sendHandler callback is invoked. That handler will either
+ /// close the connection gracefully if all data has been sent, or will
+ /// call @ref doSend() again to send the next chunk of data.
+ void doSend() {
+ size_t chunk_size = (response_.size() < BUF_SIZE) ? response_.size() : BUF_SIZE;
+ socket_->asyncSend(&response_[0], chunk_size,
+ boost::bind(&Connection::sendHandler, shared_from_this(), _1, _2));
+ }
-bool CommandMgr::closeConnection(int fd) {
+ /// @brief Handler invoked when the data is received over the control
+ /// socket.
+ ///
+ /// It collects received data into the @c isc::config::JSONFeed object and
+ /// schedules additional asynchronous read of data if this object signals
+ /// that command is incomplete. When the entire command is received, the
+ /// handler processes this command and asynchronously responds to the
+ /// controlling client.
+ //
+ ///
+ /// @param ec Error code.
+ /// @param bytes_transferred Number of bytes received.
+ void receiveHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred);
+
+
+ /// @brief Handler invoked when the data is sent over the control socket.
+ ///
+ /// If there are still data to be sent, another asynchronous send is
+ /// scheduled. When the entire command is sent, the connection is shutdown
+ /// and closed.
+ ///
+ /// @param ec Error code.
+ /// @param bytes_transferred Number of bytes sent.
+ void sendHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred);
+
+ /// @brief Handler invoked when timeout has occurred.
+ ///
+ /// Asynchronously sends a response to the client indicating that the
+ /// timeout has occurred.
+ void timeoutHandler();
+
+private:
+
+ /// @brief Pointer to the socket used for transmission.
+ boost::shared_ptr<UnixDomainSocket> socket_;
+
+ /// @brief Interval timer used to detect connection timeouts.
+ IntervalTimer timeout_timer_;
+
+ /// @brief Connection timeout (in seconds)
+ unsigned short timeout_;
+
+ /// @brief Buffer used for received data.
+ std::array<char, BUF_SIZE> buf_;
+
+ /// @brief Response created by the server.
+ std::string response_;
+
+ /// @brief Reference to the pool of connections.
+ ConnectionPool& connection_pool_;
+
+ /// @brief State model used to receive data over the connection and detect
+ /// when the command ends.
+ JSONFeed feed_;
+
+ /// @brief Boolean flag indicating if the request to stop connection is a
+ /// result of server reconfiguration.
+ bool response_in_progress_;
+
+};
+
+/// @brief Pointer to the @c Connection.
+typedef boost::shared_ptr<Connection> ConnectionPtr;
+
+/// @brief Holds all open connections.
+class ConnectionPool {
+public:
+
+ /// @brief Starts new connection.
+ ///
+ /// @param connection Pointer to the new connection object.
+ void start(const ConnectionPtr& connection) {
+ connection->doReceive();
+ connections_.insert(connection);
+ }
- // Let's iterate over all currently registered connections.
- for (std::list<CommandSocketPtr>::iterator conn = connections_.begin();
- conn != connections_.end(); ++conn) {
+ /// @brief Stops running connection.
+ ///
+ /// @param connection Pointer to the new connection object.
+ void stop(const ConnectionPtr& connection) {
+ try {
+ connection->stop();
+ connections_.erase(connection);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CLOSE_FAIL)
+ .arg(ex.what());
+ }
+ }
- // If found, close it.
- if ((*conn)->getFD() == fd) {
- (*conn)->close();
- connections_.erase(conn);
- return (true);
+ /// @brief Stops all connections which are allowed to stop.
+ void stopAll() {
+ for (auto conn = connections_.begin(); conn != connections_.end();
+ ++conn) {
+ (*conn)->stop();
}
+ connections_.clear();
}
- return (false);
-}
+private:
-CommandMgr&
-CommandMgr::instance() {
- static CommandMgr cmd_mgr;
- return (cmd_mgr);
-}
+ /// @brief Pool of connections.
+ std::set<ConnectionPtr> connections_;
+
+};
-void CommandMgr::registerCommand(const std::string& cmd, CommandHandler handler) {
+void
+Connection::terminate() {
+ try {
+ socket_->shutdown();
- if (!handler) {
- isc_throw(InvalidCommandHandler, "Specified command handler is NULL");
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL)
+ .arg(ex.what());
}
+}
+
+void
+Connection::receiveHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred) {
+ if (ec) {
+ if (ec.value() == boost::asio::error::eof) {
+ // Foreign host has closed the connection. We should remove it from the
+ // connection pool.
+ LOG_INFO(command_logger, COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST)
+ .arg(socket_->getNative());
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_READ_FAIL)
+ .arg(ec.value()).arg(socket_->getNative());
+ }
+
+ connection_pool_.stop(shared_from_this());
+ return;
- HandlerContainer::const_iterator it = handlers_.find(cmd);
- if (it != handlers_.end()) {
- isc_throw(InvalidCommandName, "Handler for command '" << cmd
- << "' is already installed.");
+ } else if (bytes_transferred == 0) {
+ // Nothing received. Close the connection.
+ connection_pool_.stop(shared_from_this());
+ return;
}
- handlers_.insert(make_pair(cmd, handler));
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ)
+ .arg(bytes_transferred).arg(socket_->getNative());
- LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_REGISTERED).arg(cmd);
-}
+ ConstElementPtr rsp;
+
+ try {
+ // Received some data over the socket. Append them to the JSON feed
+ // to see if we have reached the end of command.
+ feed_.postBuffer(&buf_[0], bytes_transferred);
+ feed_.poll();
+ // If we haven't yet received the full command, continue receiving.
+ if (feed_.needData()) {
+ doReceive();
+ return;
+ }
-void CommandMgr::deregisterCommand(const std::string& cmd) {
- if (cmd == "list-commands") {
- isc_throw(InvalidCommandName,
- "Can't uninstall internal command 'list-commands'");
+ // Received entire command. Parse the command into JSON.
+ if (feed_.feedOk()) {
+ ConstElementPtr cmd = feed_.toElement();
+ response_in_progress_ = true;
+
+ // If successful, then process it as a command.
+ rsp = CommandMgr::instance().processCommand(cmd);
+
+ response_in_progress_ = false;
+
+ } else {
+ // Failed to parse command as JSON or process the received command.
+ // This exception will be caught below and the error response will
+ // be sent.
+ isc_throw(BadValue, feed_.getErrorMessage());
+ }
+
+ } catch (const Exception& ex) {
+ LOG_WARN(command_logger, COMMAND_PROCESS_ERROR1).arg(ex.what());
+ rsp = createAnswer(CONTROL_RESULT_ERROR, std::string(ex.what()));
}
- HandlerContainer::iterator it = handlers_.find(cmd);
- if (it == handlers_.end()) {
- isc_throw(InvalidCommandName, "Handler for command '" << cmd
- << "' not found.");
+ // No response generated. Connection will be closed.
+ if (!rsp) {
+ LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR);
+ rsp = createAnswer(CONTROL_RESULT_ERROR,
+ "internal server error: no response generated");
+
+ } else {
+
+ // Let's convert JSON response to text. Note that at this stage
+ // the rsp pointer is always set.
+ response_ = rsp->str();
+
+ doSend();
+ return;
}
- handlers_.erase(it);
- LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_DEREGISTERED).arg(cmd);
+ // Close the connection if we have sent the entire response.
+ connection_pool_.stop(shared_from_this());
}
-void CommandMgr::deregisterAll() {
+void
+Connection::sendHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred) {
+ if (ec) {
+ // If an error occurred, log this error and stop the connection.
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL)
+ .arg(socket_->getNative()).arg(ec.message());
+ }
+
+ } else {
+ // No error. We are in a process of sending a response. Need to
+ // remove the chunk that we have managed to sent with the previous
+ // attempt.
+ response_.erase(0, bytes_transferred);
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE)
+ .arg(bytes_transferred).arg(response_.size())
+ .arg(socket_->getNative());
+
+ // Check if there is any data left to be sent and sent it.
+ if (!response_.empty()) {
+ doSend();
+ return;
+ }
+
+ // Gracefully shutdown the connection and close the socket if
+ // we have sent the whole response.
+ terminate();
+ }
- // No need to log anything here. deregisterAll is not used in production
- // code, just in tests.
- handlers_.clear();
- registerCommand("list-commands",
- boost::bind(&CommandMgr::listCommandsHandler, this, _1, _2));
+ // All data sent or an error has occurred. Close the connection.
+ connection_pool_.stop(shared_from_this());
}
void
-CommandMgr::commandReader(int sockfd) {
+Connection::timeoutHandler() {
+ LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_TIMEOUT)
+ .arg(socket_->getNative());
- /// @todo: We do not handle commands that are larger than 64K.
+ try {
+ socket_->cancel();
- // We should not expect commands bigger than 64K.
- char buf[65536];
- memset(buf, 0, sizeof(buf));
- ConstElementPtr cmd, rsp;
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CANCEL_FAIL)
+ .arg(socket_->getNative())
+ .arg(ex.what());
+ }
- // Read incoming data.
- int rval = read(sockfd, buf, sizeof(buf));
- if (rval < 0) {
- // Read failed
- LOG_ERROR(command_logger, COMMAND_SOCKET_READ_FAIL).arg(rval).arg(sockfd);
+ ConstElementPtr rsp = createAnswer(CONTROL_RESULT_ERROR, "Connection over"
+ " control channel timed out");
+ response_ = rsp->str();
+ doSend();
+}
- /// @todo: Should we close the connection, similar to what is already
- /// being done for rval == 0?
- return;
- } else if (rval == 0) {
- // Remove it from the active connections list.
- instance().closeConnection(sockfd);
+}
- return;
+namespace isc {
+namespace config {
+
+/// @brief Implementation of the @c CommandMgr.
+class CommandMgrImpl {
+public:
+
+ /// @brief Constructor.
+ CommandMgrImpl()
+ : io_service_(), acceptor_(), socket_(), socket_name_(),
+ connection_pool_(), timeout_(DEFAULT_CONNECTION_TIMEOUT) {
}
- LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ).arg(rval).arg(sockfd);
+ /// @brief Opens acceptor service allowing the control clients to connect.
+ ///
+ /// @param socket_info Configuration information for the control socket.
+ /// @throw BadSocketInfo When socket configuration is invalid.
+ /// @throw SocketError When socket operation fails.
+ void openCommandSocket(const isc::data::ConstElementPtr& socket_info);
- // Ok, we received something. Let's see if we can make any sense of it.
- try {
+ /// @brief Asynchronously accepts next connection.
+ void doAccept();
- // Try to interpret it as JSON.
- std::string sbuf(buf, static_cast<size_t>(rval));
- cmd = Element::fromJSON(sbuf, true);
+ /// @brief Pointer to the IO service used by the server process for running
+ /// asynchronous tasks.
+ IOServicePtr io_service_;
- // If successful, then process it as a command.
- rsp = CommandMgr::instance().processCommand(cmd);
- } catch (const Exception& ex) {
- LOG_WARN(command_logger, COMMAND_PROCESS_ERROR1).arg(ex.what());
- rsp = createAnswer(CONTROL_RESULT_ERROR, std::string(ex.what()));
- }
+ /// @brief Pointer to the acceptor service.
+ boost::shared_ptr<UnixDomainSocketAcceptor> acceptor_;
- if (!rsp) {
- LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR);
- return;
- }
+ /// @brief Pointer to the socket into which the new connection is accepted.
+ boost::shared_ptr<UnixDomainSocket> socket_;
+
+ /// @brief Path to the unix domain socket descriptor.
+ ///
+ /// This is used to remove the socket file once the connection terminates.
+ std::string socket_name_;
+
+ /// @brief Pool of connections.
+ ConnectionPool connection_pool_;
- // Let's convert JSON response to text. Note that at this stage
- // the rsp pointer is always set.
- std::string txt = rsp->str();
- size_t len = txt.length();
- if (len > 65535) {
- // Hmm, our response is too large. Let's send the first
- // 64KB and hope for the best.
- LOG_ERROR(command_logger, COMMAND_SOCKET_RESPONSE_TOOLARGE).arg(len);
+ /// @brief Connection timeout
+ unsigned short timeout_;
+};
- len = 65535;
+void
+CommandMgrImpl::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
+ socket_name_.clear();
+
+ if(!socket_info) {
+ isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
}
- // Send the data back over socket.
- rval = write(sockfd, txt.c_str(), len);
+ ConstElementPtr type = socket_info->get("socket-type");
+ if (!type) {
+ isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
+ }
- LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE).arg(len).arg(sockfd);
+ // Only supporting unix sockets right now.
+ if (type->stringValue() != "unix") {
+ isc_throw(BadSocketInfo, "Invalid 'socket-type' parameter value "
+ << type->stringValue());
+ }
- if (rval < 0) {
- // Response transmission failed. Since the response failed, it doesn't
- // make sense to send any status codes. Let's log it and be done with
- // it.
- LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL).arg(len).arg(sockfd);
+ // UNIX socket is requested. It takes one parameter: socket-name that
+ // specifies UNIX path of the socket.
+ ConstElementPtr name = socket_info->get("socket-name");
+ if (!name) {
+ isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
}
-}
-isc::data::ConstElementPtr
-CommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
- if (!cmd) {
- return (createAnswer(CONTROL_RESULT_ERROR,
- "Command processing failed: NULL command parameter"));
+ if (name->getType() != Element::string) {
+ isc_throw(BadSocketInfo, "'socket-name' parameter expected to be a string");
}
+ socket_name_ = name->stringValue();
+
try {
- ConstElementPtr arg;
- std::string name = parseCommand(arg, cmd);
+ // Start asynchronous acceptor service.
+ acceptor_.reset(new UnixDomainSocketAcceptor(*io_service_));
+ UnixDomainSocketEndpoint endpoint(socket_name_);
+ acceptor_->open(endpoint);
+ acceptor_->bind(endpoint);
+ acceptor_->listen();
+
+ // Install this socket in Interface Manager.
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(acceptor_->getNative(), 0);
- LOG_INFO(command_logger, COMMAND_RECEIVED).arg(name);
+ doAccept();
- HandlerContainer::const_iterator it = handlers_.find(name);
- if (it == handlers_.end()) {
- // Ok, there's no such command.
- return (createAnswer(CONTROL_RESULT_ERROR,
- "'" + name + "' command not supported."));
+ } catch (const std::exception& ex) {
+ isc_throw(SocketError, ex.what());
+ }
+}
+
+void
+CommandMgrImpl::doAccept() {
+ // Create a socket into which the acceptor will accept new connection.
+ socket_.reset(new UnixDomainSocket(*io_service_));
+ acceptor_->asyncAccept(*socket_, [this](const boost::system::error_code& ec) {
+ if (!ec) {
+ // New connection is arriving. Start asynchronous transmission.
+ ConnectionPtr connection(new Connection(io_service_, socket_,
+ connection_pool_,
+ timeout_));
+ connection_pool_.start(connection);
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_ACCEPT_FAIL)
+ .arg(acceptor_->getNative()).arg(ec.message());
}
- // Call the actual handler and return whatever it returned
- return (it->second(name, arg));
+ // Unless we're stopping the service, start accepting connections again.
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ doAccept();
+ }
+ });
+}
- } catch (const Exception& e) {
- LOG_WARN(command_logger, COMMAND_PROCESS_ERROR2).arg(e.what());
- return (createAnswer(CONTROL_RESULT_ERROR,
- std::string("Error during command processing:")
- + e.what()));
- }
+CommandMgr::CommandMgr()
+ : HookedCommandMgr(), impl_(new CommandMgrImpl()) {
}
-isc::data::ConstElementPtr
-CommandMgr::listCommandsHandler(const std::string& name,
- const isc::data::ConstElementPtr& params) {
- using namespace isc::data;
- ElementPtr commands = Element::createList();
- for (HandlerContainer::const_iterator it = handlers_.begin();
- it != handlers_.end(); ++it) {
- commands->add(Element::create(it->first));
+void
+CommandMgr::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
+ impl_->openCommandSocket(socket_info);
+}
+
+void CommandMgr::closeCommandSocket() {
+ // Close acceptor if the acceptor is open.
+ if (impl_->acceptor_ && impl_->acceptor_->isOpen()) {
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(impl_->acceptor_->getNative());
+ impl_->acceptor_->close();
+ static_cast<void>(::remove(impl_->socket_name_.c_str()));
}
- return (createAnswer(CONTROL_RESULT_SUCCESS, commands));
+
+ // Stop all connections which can be closed. The only connection that won't
+ // be closed is the one over which we have received a request to reconfigure
+ // the server. This connection will be held until the CommandMgr responds to
+ // such request.
+ impl_->connection_pool_.stopAll();
+}
+
+int
+CommandMgr::getControlSocketFD() {
+ return (impl_->acceptor_ ? impl_->acceptor_->getNative() : -1);
}
+
+CommandMgr&
+CommandMgr::instance() {
+ static CommandMgr cmd_mgr;
+ return (cmd_mgr);
+}
+
+void
+CommandMgr::setIOService(const IOServicePtr& io_service) {
+ impl_->io_service_ = io_service;
+}
+
+void
+CommandMgr::setConnectionTimeout(const unsigned short timeout) {
+ impl_->timeout_ = timeout;
+}
+
+
}; // end of isc::config
}; // end of isc
diff --git a/src/lib/config/command_mgr.h b/src/lib/config/command_mgr.h
index 976b2223b1..5eb23015fb 100644
--- a/src/lib/config/command_mgr.h
+++ b/src/lib/config/command_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,187 +7,85 @@
#ifndef COMMAND_MGR_H
#define COMMAND_MGR_H
+#include <asiolink/io_service.h>
#include <cc/data.h>
-#include <config/command_socket.h>
+#include <config/hooked_command_mgr.h>
+#include <exceptions/exceptions.h>
#include <boost/noncopyable.hpp>
-#include <boost/function.hpp>
-#include <string>
-#include <list>
-#include <map>
+#include <boost/shared_ptr.hpp>
namespace isc {
namespace config {
-/// @brief CommandMgr exception indicating that the handler specified is not valid
-class InvalidCommandHandler : public Exception {
+/// @brief An exception indicating that specified socket parameters are invalid
+class BadSocketInfo : public Exception {
public:
- InvalidCommandHandler(const char* file, size_t line, const char* what) :
+ BadSocketInfo(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
-/// @brief CommandMgr exception indicating that the command name is not valid
-class InvalidCommandName : public Exception {
+/// @brief An exception indicating a problem with socket operation
+class SocketError : public Exception {
public:
- InvalidCommandName(const char* file, size_t line, const char* what) :
+ SocketError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
-/// @brief Commands Manager, responsible for processing external commands
-///
-/// Commands Manager is a generic interface for handling external commands.
-/// Commands can be received over control sockets. Currently unix socket is
-/// supported, but additional type (udp, tcp, https etc.) may be added later.
-/// The commands and responses are sent in JSON format.
-/// See http://kea.isc.org/wiki/StatsDesign for details.
-///
-/// In general, the command has the following format:
-/// {
-/// "command": "statistic-get",
-/// "arguments": {
-/// "name": "received-packets"
-/// }
-/// }
-///
-/// And the response is:
-///
-/// {
-/// "result": 0,
-/// "observations": {
-/// "received-packets": [ [ 1234, "2015-04-15 12:34:45.123" ] ]
-/// }
-/// }
+
+class CommandMgrImpl;
+
+/// @brief Commands Manager implementation for the Kea servers.
///
-/// CommandsMgr does not implement the commands (except one, "list-commands")
-/// itself, but rather provides an interface (see @ref registerCommand,
-/// @ref deregisterCommand, @ref processCommand) for other components to use
-/// it. The @ref CommandHandler type is specified in a way to easily use
-/// existing command handlers in DHCPv4 and DHCPv6 components.
-class CommandMgr : public boost::noncopyable {
+/// This class extends @ref BaseCommandMgr with the ability to receive and
+/// respond to commands over unix domain sockets.
+class CommandMgr : public HookedCommandMgr, public boost::noncopyable {
public:
- /// @brief Defines command handler type
- ///
- /// Command handlers are expected to use this format.
- /// @param name name of the commands
- /// @param params parameters specific to the command
- /// @return response (created with createAnswer())
- typedef boost::function<isc::data::ConstElementPtr (const std::string& name,
- const isc::data::ConstElementPtr& params)> CommandHandler;
-
/// @brief CommandMgr is a singleton class. This method returns reference
/// to its sole instance.
///
/// @return the only existing instance of the manager
static CommandMgr& instance();
- /// @brief Opens control socket with paramters specified in socket_info
+ /// @brief Sets IO service to be used by the command manager.
+ ///
+ /// The server should use this method to provide the Command Manager with the
+ /// common IO service used by the server.
+ /// @param io_service Pointer to the IO service.
+ void setIOService(const asiolink::IOServicePtr& io_service);
+
+ /// @brief Override default connection timeout.
+ ///
+ /// @param timeout New connection timeout in seconds.
+ void setConnectionTimeout(const unsigned short timeout);
+
+ /// @brief Opens control socket with parameters specified in socket_info
///
/// Currently supported types are:
/// - unix (required parameters: socket-type: unix, socket-name:/unix/path)
///
- /// This method will close previously open command socket (if exists).
+ /// @throw BadSocketInfo When socket configuration is invalid.
+ /// @throw SocketError When socket operation fails.
///
- /// @throw CommandSocketError if socket creation fails.
- /// @throw SocketError if command socket is already open.
- ///
- /// @param socket_info describes control socket parameters
- /// @return object representing a socket
- CommandSocketPtr
+ /// @param socket_info Configuration information for the control socket.
+ void
openCommandSocket(const isc::data::ConstElementPtr& socket_info);
/// @brief Shuts down any open control sockets
void closeCommandSocket();
- /// @brief Registers specified command handler for a given command
- ///
- /// @param cmd name of the command to be handled
- /// @param handler pointer to the method that will handle the command
- void registerCommand(const std::string& cmd, CommandHandler handler);
-
- /// @brief Deregisters specified command handler
- ///
- /// @param cmd name of the command that's no longer handled
- void deregisterCommand(const std::string& cmd);
-
- /// @brief Triggers command processing
- ///
- /// This method processes specified command. The command is specified using
- /// a single Element. See @ref CommandMgr for description of its syntax.
- /// Typically, this method is called internally, when there's a new data
- /// received over control socket. However, in some cases (e.g. signal received)
- /// it may be called by external code explicitly. Hence this method is public.
- isc::data::ConstElementPtr processCommand(const isc::data::ConstElementPtr& cmd);
-
- /// @brief Reads data from a socket, parses as JSON command and processes it
- ///
- /// This method is used to handle traffic on connected socket. This callback
- /// is installed by the @c isc::config::UnixCommandSocket::receiveHandler
- /// (located in the src/lib/config/command_socket_factory.cc)
- /// once the incoming connection is accepted. If end-of-file is detected, this
- /// method will close the socket and will uninstall itself from
- /// @ref isc::dhcp::IfaceMgr.
- ///
- /// @param sockfd socket descriptor of a connected socket
- static void commandReader(int sockfd);
-
- /// @brief Auxiliary method that removes all installed commands.
- ///
- /// The only unwipeable method is list-commands, which is internally
- /// handled at all times.
- void deregisterAll();
-
- /// @brief Adds an information about opened connection socket
- ///
- /// @param conn Connection socket to be stored
- void addConnection(const CommandSocketPtr& conn);
-
- /// @brief Closes connection with a specific socket descriptor
- ///
- /// @param fd socket descriptor
- /// @return true if closed successfully, false if not found
- bool closeConnection(int fd);
-
/// @brief Returns control socket descriptor
///
/// This method should be used only in tests.
- int getControlSocketFD() const {
- return (socket_->getFD());
- }
+ int getControlSocketFD();
private:
/// @brief Private constructor
- ///
- /// Registers internal 'list-commands' command.
CommandMgr();
- /// @brief 'list-commands' command handler
- ///
- /// This method implements command 'list-commands'. It returns a list of all
- /// currently supported commands.
- /// @param name name of the command (should always be 'list-commands')
- /// @param params additional parameters (ignored)
- /// @return structure that includes all currently supported commands
- isc::data::ConstElementPtr
- listCommandsHandler(const std::string& name,
- const isc::data::ConstElementPtr& params);
-
- typedef std::map<std::string, CommandHandler> HandlerContainer;
-
- /// @brief Container for command handlers
- HandlerContainer handlers_;
-
- /// @brief Control socket structure
- ///
- /// This is the socket that accepts incoming connections. There can be at
- /// most one (if command channel is configured).
- CommandSocketPtr socket_;
-
- /// @brief Sockets for open connections
- ///
- /// These are the sockets that are dedicated to handle a specific connection.
- /// Their number is equal to number of current control connections.
- std::list<CommandSocketPtr> connections_;
+ /// @brief Pointer to the implementation of the @ref CommandMgr.
+ boost::shared_ptr<CommandMgrImpl> impl_;
};
}; // end of isc::config namespace
diff --git a/src/lib/config/command_socket.cc b/src/lib/config/command_socket.cc
deleted file mode 100644
index 0f4cf2c9d2..0000000000
--- a/src/lib/config/command_socket.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#include <config/command_socket.h>
-#include <config/command_mgr.h>
-#include <config/config_log.h>
-#include <dhcp/iface_mgr.h>
-#include <boost/bind.hpp>
-#include <unistd.h>
-
-namespace isc {
-namespace config {
-
-ConnectionSocket::ConnectionSocket(int sockfd) {
- sockfd_ = sockfd;
-
- // Install commandReader callback. When there's any data incoming on this
- // socket, commandReader will be called and process it. It may also
- // eventually close this socket.
- isc::dhcp::IfaceMgr::instance().addExternalSocket(sockfd,
- boost::bind(&ConnectionSocket::receiveHandler, this));
- }
-
-void ConnectionSocket::close() {
- LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_CLOSED).arg(sockfd_);
-
- // Unregister this callback
- isc::dhcp::IfaceMgr::instance().deleteExternalSocket(sockfd_);
-
- // We're closing a connection, not the whole socket. It's ok to just
- // close the connection and don't delete anything.
- ::close(sockfd_);
-}
-
-void ConnectionSocket::receiveHandler() {
- CommandMgr::instance().commandReader(sockfd_);
-}
-
-};
-};
diff --git a/src/lib/config/command_socket.h b/src/lib/config/command_socket.h
deleted file mode 100644
index 134c223309..0000000000
--- a/src/lib/config/command_socket.h
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#ifndef COMMAND_SOCKET_H
-#define COMMAND_SOCKET_H
-
-#include <cc/data.h>
-#include <unistd.h>
-
-namespace isc {
-namespace config {
-
-/// @brief An exception indicating that specified socket parameters are invalid
-class BadSocketInfo : public Exception {
-public:
- BadSocketInfo(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) { };
-};
-
-/// @brief An exception indicating a problem with socket operation
-class SocketError : public Exception {
-public:
- SocketError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) { };
-};
-
-/// @brief Abstract base class that represents an open command socket
-///
-/// Derived classes are expected to handle specific socket types (e.g. UNIX
-/// or https).
-///
-/// For derived classes, see @ref UnixCommandSocket for a socket that
-/// accepts connections over UNIX socket and @ref ConnectionSocket that
-/// handles established connections (currently over UNIX sockets, but
-/// should be generic).
-class CommandSocket {
-public:
- /// @brief Method used to handle incoming data
- ///
- /// This may be registered in @ref isc::dhcp::IfaceMgr
- virtual void receiveHandler() = 0;
-
- /// @brief General method for closing socket.
- ///
- /// This is the default implementation that simply closes
- /// the socket. Derived classes may do additional steps
- /// to terminate the connection.
- virtual void close() {
- ::close(sockfd_);
- }
-
- /// @brief Virtual destructor.
- virtual ~CommandSocket() {
- close();
- }
-
- /// @brief Returns socket descriptor.
- int getFD() const {
- return (sockfd_);
- }
-
-protected:
- /// Stores socket descriptor.
- int sockfd_;
-};
-
-/// Pointer to a command socket object
-typedef boost::shared_ptr<CommandSocket> CommandSocketPtr;
-
-/// @brief This class represents a streaming socket for handling connections
-///
-/// Initially a socket (e.g. UNIX) is opened (represented by other classes, e.g.
-/// @ref UnixCommandSocket). Once incoming connection is detected, that class
-/// calls accept(), which returns a new socket dedicated to handling that
-/// specific connection. That socket is represented by this class.
-class ConnectionSocket : public CommandSocket {
-public:
- /// @brief Default constructor
- ///
- /// This constructor is used in methods that call accept on existing
- /// sockets. accept() returns a socket descriptor. Hence only one
- /// parameter here.
- ///
- /// @param sockfd socket descriptor
- ConnectionSocket(int sockfd);
-
- /// @brief Method used to handle incoming data
- ///
- /// This method calls isc::config::CommandMgr::commandReader method.
- virtual void receiveHandler();
-
- /// @brief Closes socket.
- ///
- /// This method closes the socket, prints appropriate log message and
- /// unregisters callback from @ref isc::dhcp::IfaceMgr.
- virtual void close();
-};
-
-};
-};
-
-#endif
diff --git a/src/lib/config/command_socket_factory.cc b/src/lib/config/command_socket_factory.cc
deleted file mode 100644
index 83534e3e9c..0000000000
--- a/src/lib/config/command_socket_factory.cc
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#include <config/command_socket_factory.h>
-#include <config/config_log.h>
-#include <config/command_mgr.h>
-#include <dhcp/iface_mgr.h>
-#include <boost/bind.hpp>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <string.h>
-#include <errno.h>
-#include <cstdio>
-#include <fcntl.h>
-
-using namespace isc::data;
-
-namespace isc {
-namespace config {
-
-/// @brief Wrapper for UNIX stream sockets
-///
-/// There are two UNIX socket types: datagram-based (equivalent of UDP) and
-/// stream-based (equivalent of TCP). This class represents stream-based
-/// sockets. It opens up a unix-socket and waits for incoming connections.
-/// Once incoming connection is detected, accept() system call is called
-/// and a new socket for that particular connection is returned. A new
-/// object of @ref ConnectionSocket is created.
-class UnixCommandSocket : public CommandSocket {
-public:
- /// @brief Default constructor
- ///
- /// Opens specified UNIX socket.
- ///
- /// @param filename socket filename
- UnixCommandSocket(const std::string& filename)
- : filename_(filename) {
-
- // Create the socket and set it up.
- sockfd_ = createUnixSocket(filename_);
-
- // Install this socket in Interface Manager.
- isc::dhcp::IfaceMgr::instance().addExternalSocket(sockfd_,
- boost::bind(&UnixCommandSocket::receiveHandler, this));
- }
-
-private:
-
- /// @brief Auxiliary method for creating a UNIX socket
- ///
- /// @param file_name specifies socket file path
- /// @return socket file descriptor
- int createUnixSocket(const std::string& file_name) {
-
- struct sockaddr_un addr;
-
- // string.size() returns number of bytes (without trailing zero)
- // we need 1 extra byte for terminating 0.
- if (file_name.size() > sizeof(addr.sun_path) - 1) {
- isc_throw(SocketError, "Failed to open socket: path specified ("
- << file_name << ") is longer (" << file_name.size()
- << " bytes) than allowed "
- << (sizeof(addr.sun_path) - 1) << " bytes.");
- }
-
- int fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (fd == -1) {
- isc_throw(isc::config::SocketError, "Failed to create AF_UNIX socket:"
- << strerror(errno));
- }
-
- // Let's remove the old file. We don't care about any possible
- // errors here. The file should not be there if the file was
- // shut down properly.
- static_cast<void>(remove(file_name.c_str()));
-
- // Set this socket to be closed-on-exec.
- if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) {
- const char* errmsg = strerror(errno);
- ::close(fd);
- isc_throw(SocketError, "Failed to set close-on-exec on unix socket\
- "
- << fd << ": " << errmsg);
- }
-
- // Set this socket to be non-blocking one.
- if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
- const char* errmsg = strerror(errno);
- ::close(fd);
- isc_throw(SocketError, "Failed to set non-block mode on unix socket "
- << fd << ": " << errmsg);
- }
-
- // Now bind the socket to the specified path.
- memset(&addr, 0, sizeof(addr));
- addr.sun_family = AF_UNIX;
- strncpy(addr.sun_path, file_name.c_str(), sizeof(addr.sun_path) - 1);
- if (bind(fd, (struct sockaddr*)&addr, sizeof(addr))) {
- const char* errmsg = strerror(errno);
- ::close(fd);
- static_cast<void>(remove(file_name.c_str()));
- isc_throw(isc::config::SocketError, "Failed to bind socket " << fd
- << " to " << file_name << ": " << errmsg);
- }
-
- // One means that we allow at most 1 awaiting connections.
- // Any additional attempts will get ECONNREFUSED error.
- // That means that at any given time, there may be at most one controlling
- // connection.
- /// @todo: Make the number of parallel connections configurable.
- int status = listen(fd, 1);
- if (status < 0) {
- const char* errmsg = strerror(errno);
- ::close(fd);
- static_cast<void>(remove(file_name.c_str()));
- isc_throw(isc::config::SocketError, "Failed to listen on socket fd="
- << fd << ", filename=" << file_name << ": " << errmsg);
- }
-
- // Woohoo! Socket opened, let's log it!
- LOG_INFO(command_logger, COMMAND_SOCKET_UNIX_OPEN).arg(fd).arg(file_name);
-
- return (fd);
- }
-
- /// @public
-
- /// @brief Connection acceptor, a callback used to accept incoming connections.
- ///
- /// This callback is used on a control socket. Once called, it will accept
- /// incoming connection, create a new socket for it and create an instance
- /// of ConnectionSocket, which will take care of the rest (i.e. install
- /// appropriate callback for that new socket in @ref isc::dhcp::IfaceMgr).
- void receiveHandler() {
-
- // This method is specific to receiving data over UNIX socket, so using
- // sockaddr_un instead of sockaddr_storage here is ok.
- struct sockaddr_un client_addr;
- socklen_t client_addr_len;
- client_addr_len = sizeof(client_addr);
-
- // Accept incoming connection. This will create a separate socket for
- // handling this specific connection.
- int fd2 = accept(sockfd_, reinterpret_cast<struct sockaddr*>(&client_addr),
- &client_addr_len);
- if (fd2 == -1) {
- LOG_ERROR(command_logger, COMMAND_SOCKET_ACCEPT_FAIL)
- .arg(sockfd_).arg(strerror(errno));
- return;
- }
-
- // And now create an object that represents that new connection.
- CommandSocketPtr conn(new ConnectionSocket(fd2));
-
- // Not sure if this is really needed, but let's set it to non-blocking
- // mode.
- if (fcntl(fd2, F_SETFL, O_NONBLOCK) != 0) {
- // Failed to set socket to non-blocking mode.
- LOG_ERROR(command_logger, COMMAND_SOCKET_FAIL_NONBLOCK)
- .arg(fd2).arg(sockfd_).arg(strerror(errno));
-
- conn.reset();
- return;
- }
-
- // Remember this socket descriptor. It will be needed when we shut down
- // the server.
- CommandMgr::instance().addConnection(conn);
-
- LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_OPENED).arg(fd2)
- .arg(sockfd_);
- }
-
- /// @private
-
- // This method is called when we shutdown the connection.
- void close() {
- LOG_INFO(command_logger, COMMAND_SOCKET_UNIX_CLOSE).arg(sockfd_)
- .arg(filename_);
-
- isc::dhcp::IfaceMgr::instance().deleteExternalSocket(sockfd_);
-
- // Close should always succeed. We don't care if we're able to delete
- // the socket or not.
- ::close(sockfd_);
- static_cast<void>(remove(filename_.c_str()));
- }
-
- /// @brief UNIX filename representing this socket
- std::string filename_;
-};
-
-CommandSocketPtr
-CommandSocketFactory::create(const isc::data::ConstElementPtr& socket_info) {
- if(!socket_info) {
- isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
- }
-
- ConstElementPtr type = socket_info->get("socket-type");
- if (!type) {
- isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
- }
-
- if (type->stringValue() == "unix") {
- // UNIX socket is requested. It takes one parameter: socket-name that
- // specifies UNIX path of the socket.
- ConstElementPtr name = socket_info->get("socket-name");
- if (!name) {
- isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
- }
- if (name->getType() != Element::string) {
- isc_throw(BadSocketInfo, "'socket-name' parameter expected to be a string");
- }
-
- return (CommandSocketPtr(new UnixCommandSocket(name->stringValue())));
- } else {
- isc_throw(BadSocketInfo, "Specified socket type ('" + type->stringValue()
- + "') is not supported.");
- }
-}
-
-};
-};
diff --git a/src/lib/config/command_socket_factory.h b/src/lib/config/command_socket_factory.h
deleted file mode 100644
index 7b9fa9f7a1..0000000000
--- a/src/lib/config/command_socket_factory.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#ifndef COMMAND_SOCKET_FACTORY_H
-#define COMMAND_SOCKET_FACTORY_H
-
-#include <cc/data.h>
-#include <config/command_socket.h>
-
-namespace isc {
-namespace config {
-
-/// A factory class for opening command socket
-///
-/// This class provides an interface for opening command socket.
-class CommandSocketFactory {
-public:
-
- /// @brief Creates a socket specified by socket_info structure
- ///
- ///
- /// Currently supported types are:
- /// - unix
- ///
- /// See @ref CommandMgr::openCommandSocket for detailed description.
- /// @throw CommandSocketError
- ///
- /// @param socket_info structure that describes the socket
- /// @return socket descriptor
- static CommandSocketPtr create(const isc::data::ConstElementPtr& socket_info);
-};
-
-};
-};
-
-#endif
diff --git a/src/lib/config/config_log.h b/src/lib/config/config_log.h
index 7142015a34..4937f4a9dc 100644
--- a/src/lib/config/config_log.h
+++ b/src/lib/config/config_log.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -21,7 +21,7 @@ namespace config {
extern isc::log::Logger command_logger;
// Enumerate configuration elements as they are processed.
-const int DBG_COMMAND = DBGLVL_COMMAND;
+const int DBG_COMMAND = isc::log::DBGLVL_COMMAND;
} // namespace config
} // namespace isc
diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes
index 2a4558fd85..a40bd2b569 100644
--- a/src/lib/config/config_messages.mes
+++ b/src/lib/config/config_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,6 +11,11 @@ This debug message indicates that the daemon stopped supporting specified
command. This command can no longer be issued. If the command socket is
open and this command is issued, the daemon will not be able to process it.
+% COMMAND_EXTENDED_REGISTERED Command %1 registered
+This debug message indicates that the daemon started supporting specified
+command. The handler for the registered command includes a parameter holding
+entire command to be processed.
+
% COMMAND_PROCESS_ERROR1 Error while processing command: %1
This warning message indicates that the server encountered an error while
processing received command. Additional information will be provided, if
@@ -43,19 +48,38 @@ This error indicates that the server detected incoming connection and executed
accept system call on said socket, but this call returned an error. Additional
information may be provided by the system as second parameter.
+% COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST Closed command socket %1 by foreign host
+This is an information message indicating that the command connection has been
+closed by a command control client.
+
+% COMMAND_SOCKET_CONNECTION_CANCEL_FAIL Failed to cancel read operation on socket %1: %2
+This error message is issued to indicate an error to cancel asynchronous read
+of the control command over the control socket. The cancel operation is performed
+when the timeout occurs during communication with a client. The error message
+includes details about the reason for failure.
+
% COMMAND_SOCKET_CONNECTION_CLOSED Closed socket %1 for existing command connection
This is an informational message that the socket created for handling
client's connection is closed. This usually means that the client disconnected,
but may also mean a timeout.
-% COMMAND_SOCKET_CONNECTION_OPENED Opened socket %1 for incoming command connection on socket %2
+% COMMAND_SOCKET_CONNECTION_CLOSE_FAIL Failed to close command connection: %1
+This error message is issued when an error occurred when closing a
+command connection and/or removing it from the connections pool. The
+detailed error is provided as an argument.
+
+% COMMAND_SOCKET_CONNECTION_OPENED Opened socket %1 for incoming command connection
This is an informational message that a new incoming command connection was
detected and a dedicated socket was opened for that connection.
-% COMMAND_SOCKET_FAIL_NONBLOCK Failed to set non-blocking mode for socket %1 created for incoming connection on socket %2: %3
-This error message indicates that the server failed to set non-blocking mode
-on just created socket. That socket was created for accepting specific
-incoming connection. Additional information may be provided as third parameter.
+% COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL Encountered error %1 while trying to gracefully shutdown socket
+This message indicates an error while trying to gracefully shutdown command
+connection. The type of the error is included in the message.
+
+% COMMAND_SOCKET_CONNECTION_TIMEOUT Timeout occurred for connection over socket %1
+This is an informational message that indicates that the timeout has
+occurred for one of the command channel connections. The response
+sent by the server indicates a timeout and is then closed.
% COMMAND_SOCKET_READ Received %1 bytes over command socket %2
This debug message indicates that specified number of bytes was received
@@ -65,27 +89,10 @@ over command socket identified by specified file descriptor.
This error message indicates that an error was encountered while
reading from command socket.
-% COMMAND_SOCKET_RESPONSE_TOOLARGE Server's response was larger (%1) than supported 64KB
-This error message indicates that the server received a command and generated
-an answer for it, but that response was larger than supported 64KB. Server
-will attempt to send the first 64KB of the response. Depending on the nature
-of this response, this may indicate a software or configuration error. Future
-Kea versions are expected to have better support for large responses.
-
-% COMMAND_SOCKET_UNIX_CLOSE Command socket closed: UNIX, fd=%1, path=%2
-This informational message indicates that the daemon closed a command
-processing socket. This was a UNIX socket. It was opened with the file
-descriptor and path specified.
-
-% COMMAND_SOCKET_UNIX_OPEN Command socket opened: UNIX, fd=%1, path=%2
-This informational message indicates that the daemon opened a command
-processing socket. This is a UNIX socket. It was opened with the file
-descriptor and path specified.
-
-% COMMAND_SOCKET_WRITE Sent response of %1 bytes over command socket %2
+% COMMAND_SOCKET_WRITE Sent response of %1 bytes (%2 bytes left to send) over command socket %3
This debug message indicates that the specified number of bytes was sent
over command socket identifier by the specified file descriptor.
-% COMMAND_SOCKET_WRITE_FAIL Error while writing %1 bytes to command socket %2
+% COMMAND_SOCKET_WRITE_FAIL Error while writing to command socket %1 : %2
This error message indicates that an error was encountered while
attempting to send a response to the command socket.
diff --git a/src/lib/config/documentation.txt b/src/lib/config/documentation.txt
index c6dd1ac72e..b4055ea074 100644
--- a/src/lib/config/documentation.txt
+++ b/src/lib/config/documentation.txt
@@ -33,7 +33,7 @@ To add a simple configuration option, let's say an int, we make it the following
"config_data" contains a list of elements of the form
{ "item_name": "name"
"item_type": "integer|real|boolean|string|list|map"
- "item_optional": True|False
+ "item_optional": true|false
"item_default": <depends on type>
}
diff --git a/src/lib/config/hooked_command_mgr.cc b/src/lib/config/hooked_command_mgr.cc
new file mode 100644
index 0000000000..c7c2e3df2b
--- /dev/null
+++ b/src/lib/config/hooked_command_mgr.cc
@@ -0,0 +1,131 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <cc/command_interpreter.h>
+#include <config/hooked_command_mgr.h>
+#include <config/config_log.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+#include <boost/pointer_cast.hpp>
+#include <vector>
+
+using namespace isc::data;
+using namespace isc::hooks;
+
+namespace isc {
+namespace config {
+
+HookedCommandMgr::HookedCommandMgr()
+ : BaseCommandMgr() {
+}
+
+bool
+HookedCommandMgr::delegateCommandToHookLibrary(const std::string& cmd_name,
+ const ConstElementPtr& params,
+ const ConstElementPtr& original_cmd,
+ ElementPtr& answer) {
+
+ ConstElementPtr hook_response;
+ if (HooksManager::commandHandlersPresent(cmd_name)) {
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Set status to normal.
+ callout_handle->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+
+ // Delete previously set arguments.
+ callout_handle->deleteAllArguments();
+
+ ConstElementPtr command = original_cmd ? original_cmd :
+ createCommand(cmd_name, params);
+
+ // And pass it to the hook library.
+ callout_handle->setArgument("command", command);
+ callout_handle->setArgument("response", hook_response);
+
+ HooksManager::callCommandHandlers(cmd_name, *callout_handle);
+
+ // The callouts should set the response.
+ callout_handle->getArgument("response", hook_response);
+
+ answer = boost::const_pointer_cast<Element>(hook_response);
+
+ return (true);
+ }
+
+ return (false);
+}
+
+ConstElementPtr
+HookedCommandMgr::handleCommand(const std::string& cmd_name,
+ const ConstElementPtr& params,
+ const ConstElementPtr& original_cmd) {
+
+ // The 'list-commands' is a special case. Hook libraries do not implement
+ // this command. We determine what commands are supported by the hook
+ // libraries by checking what hook points are present that have callouts
+ // registered.
+ if ((cmd_name != "list-commands")) {
+ ElementPtr hook_response;
+ // Check if there are any hooks libraries to process this command.
+ if (delegateCommandToHookLibrary(cmd_name, params, original_cmd,
+ hook_response)) {
+ // Hooks libraries processed this command so simply return a
+ // result.
+ return (hook_response);
+ }
+
+ }
+
+ // If we're here it means that the callouts weren't called. We need
+ // to handle the command using local Command Mananger.
+ ConstElementPtr response = BaseCommandMgr::handleCommand(cmd_name,
+ params,
+ original_cmd);
+
+ // If we're processing 'list-commands' command we may need to include
+ // commands supported by hooks libraries in the response.
+ if (cmd_name == "list-commands") {
+ // Hooks names can be used to decode what commands are supported.
+ const std::vector<std::string>& hooks =
+ ServerHooks::getServerHooksPtr()->getHookNames();
+
+ // Only update the response if there are any hooks present.
+ if (!hooks.empty()) {
+ ElementPtr hooks_commands = Element::createList();
+ for (auto h = hooks.cbegin(); h != hooks.end(); ++h) {
+ // Try to convert hook name to command name. If non-empty
+ // string is returned it means that the hook point may have
+ // command handlers associated with it. Otherwise, it means that
+ // existing hook points are not for command handlers but for
+ // regular callouts.
+ std::string command_name = ServerHooks::hookToCommandName(*h);
+ if (!command_name.empty()) {
+ // Final check: are command handlers registered for this
+ // hook point? If there are no command handlers associated,
+ // it means that the hook library was already unloaded.
+ if (HooksManager::commandHandlersPresent(command_name)) {
+ hooks_commands->add(Element::create(command_name));
+ }
+ }
+ }
+
+ // If there is at least one hook point with command handlers
+ // registered
+ // for it, combine the lists of commands.
+ if (!hooks_commands->empty()) {
+ response = combineCommandsLists(response, createAnswer(0, hooks_commands));
+ }
+ }
+ }
+
+ return (response);
+}
+
+
+} // end of namespace isc::config
+} // end of namespace isc
diff --git a/src/lib/config/hooked_command_mgr.h b/src/lib/config/hooked_command_mgr.h
new file mode 100644
index 0000000000..fc5c258385
--- /dev/null
+++ b/src/lib/config/hooked_command_mgr.h
@@ -0,0 +1,91 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HOOKED_COMMAND_MGR_H
+#define HOOKED_COMMAND_MGR_H
+
+#include <cc/data.h>
+#include <config/base_command_mgr.h>
+
+namespace isc {
+namespace config {
+
+/// @brief Command Manager which can delegate commands to a hook library.
+///
+/// This class extends @ref BaseCommandMgr with the logic to delegate the
+/// commands to a hook library if the hook library is installed and provides
+/// command handlers for the control API.
+///
+/// The command handlers are registered by a hook library by calling
+/// @ref isc::hooks::LibraryHandle::registerCommandCallout. This call
+/// creates a hook point for this command (if one doesn't exist) and then
+/// registers the specified handler(s). When the @ref HookedCommandMgr
+/// receives a command for processing it calls the
+/// @ref isc::hooks::HooksManager::commandHandlersPresent to check if there
+/// are handlers present for this command. If so, the @ref HookedCommandMgr
+/// calls @ref isc::hooks::HooksManager::callCommandHandlers to process
+/// the command in the hooks libraries. If command handlers are not installed
+/// for this command, the @ref HookedCommandMgr will try to process the
+/// command on its own.
+///
+/// The @ref isc::hooks::CalloutHandle::CalloutNextStep flag setting by the
+/// command handlers does NOT have any influence on the operation of the
+/// @ref HookedCommandMgr, i.e. it will always skip processing command on
+/// its own if the command handlers are present for the given command, even
+/// if the handlers return an error code.
+class HookedCommandMgr : public BaseCommandMgr {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes callout handle used by the Command Manager.
+ HookedCommandMgr();
+
+protected:
+
+ /// @brief Handles the command within the hooks libraries.
+ ///
+ /// This method checks if the hooks libraries are installed which implement
+ /// command handlers for the specified command to be processed. If the
+ /// command handlers are present, this method calls them to create a response
+ /// and then passes the response back within the @c answer argument.
+ ///
+ /// Values of all arguments can be modified by the hook library.
+ ///
+ /// @param cmd_name Command name.
+ /// @param params Command arguments.
+ /// @param original_cmd Original command received.
+ /// @param [out] answer Command processing result returned by the hook.
+ ///
+ /// @return Boolean value indicating if any callouts have been executed.
+ bool
+ delegateCommandToHookLibrary(const std::string& cmd_name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original_cmd,
+ isc::data::ElementPtr& answer);
+
+ /// @brief Handles the command having a given name and arguments.
+ ///
+ /// This method calls @ref HookedCommandMgr::delegateCommandToHookLibrary to
+ /// try to process the command with the hook libraries, if they are installed.
+ ///
+ /// @param cmd_name Command name.
+ /// @param params Command arguments.
+ /// @param original_cmd Original command received.
+ ///
+ /// @return Pointer to the const data element representing response
+ /// to a command.
+ virtual isc::data::ConstElementPtr
+ handleCommand(const std::string& cmd_name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original_cmd);
+
+};
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
index b2e547ad44..d2b6bbe160 100644
--- a/src/lib/config/tests/Makefile.am
+++ b/src/lib/config/tests/Makefile.am
@@ -19,19 +19,21 @@ TESTS =
if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = module_spec_unittests.cc
-run_unittests_SOURCES += command_socket_factory_unittests.cc
+run_unittests_SOURCES += client_connection_unittests.cc
run_unittests_SOURCES += config_data_unittests.cc run_unittests.cc
run_unittests_SOURCES += command_mgr_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
-run_unittests_LDADD = $(top_builddir)/src/lib/config/libkea-cfgclient.la
+run_unittests_LDADD = $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
+run_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
run_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
diff --git a/src/lib/config/tests/client_connection_unittests.cc b/src/lib/config/tests/client_connection_unittests.cc
new file mode 100644
index 0000000000..1502bb8481
--- /dev/null
+++ b/src/lib/config/tests/client_connection_unittests.cc
@@ -0,0 +1,186 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/testutils/test_server_unix_socket.h>
+#include <cc/json_feed.h>
+#include <config/client_connection.h>
+#include <gtest/gtest.h>
+#include <cstdlib>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+
+namespace {
+
+/// @brief Test unix socket file name.
+const std::string TEST_SOCKET = "test-socket";
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// Test fixture class for @ref ClientConnection.
+class ClientConnectionTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Removes unix socket descriptor before the test.
+ ClientConnectionTest() :
+ io_service_(),
+ test_socket_(new test::TestServerUnixSocket(io_service_,
+ unixSocketFilePath())) {
+ removeUnixSocketFile();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes unix socket descriptor after the test.
+ virtual ~ClientConnectionTest() {
+ removeUnixSocketFile();
+ }
+
+ /// @brief Returns socket file path.
+ ///
+ /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
+ /// socket file is created in the location pointed to by this variable.
+ /// Otherwise, it is created in the build directory.
+ ///
+ /// The KEA_SOCKET_TEST_DIR is typically used to overcome the problem of
+ /// a system limit on the unix socket file path (usually 102 or 103 characters).
+ /// When Kea build is located in the nested directories with absolute path
+ /// exceeding this limit, the test system should be configured to set
+ /// the KEA_SOCKET_TEST_DIR environmental variable to point to an alternative
+ /// location, e.g. /tmp, with an absolute path length being within the
+ /// allowed range.
+ static std::string unixSocketFilePath() {
+ std::ostringstream s;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ s << std::string(env);
+ } else {
+ s << TEST_DATA_BUILDDIR;
+ }
+
+ s << "/" << TEST_SOCKET;
+ return (s.str());
+ }
+
+ /// @brief Removes unix socket descriptor.
+ void removeUnixSocketFile() {
+ static_cast<void>(remove(unixSocketFilePath().c_str()));
+ }
+
+ /// @brief IO service used by the tests.
+ IOService io_service_;
+
+ /// @brief Server side unix socket used in these tests.
+ test::TestServerUnixSocketPtr test_socket_;
+};
+
+// Tests successful transaction: connect, send command and receive a
+// response.
+TEST_F(ClientConnectionTest, success) {
+ // Start timer protecting against test timeouts.
+ test_socket_->startTimer(TEST_TIMEOUT);
+
+ // Start the server.
+ test_socket_->bindServerSocket();
+ test_socket_->generateCustomResponse(2048);
+
+ // Create some valid command.
+ std::string command = "{ \"command\": \"list-commands\" }";
+
+ ClientConnection conn(io_service_);
+
+ // This boolean value will indicate when the callback function is invoked
+ // at the end of the transaction (whether it is successful or unsuccessful).
+ bool handler_invoked = false;
+ conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
+ ClientConnection::ControlCommand(command),
+ [this, &handler_invoked](const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& feed) {
+ // Indicate that the handler has been called to break from the
+ // while loop below.
+ handler_invoked = true;
+ // The ec should contain no error.
+ ASSERT_FALSE(ec);
+ // The JSONFeed should be present and it should contain a valid
+ // response.
+ ASSERT_TRUE(feed);
+ EXPECT_TRUE(feed->feedOk()) << feed->getErrorMessage();
+ });
+ // Run the connection.
+ while (!handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+// This test checks that a timeout is signalled when the communication
+// takes too long.
+TEST_F(ClientConnectionTest, timeout) {
+ // The server will return only partial JSON response (lacking closing
+ // brace). The client will wait for closing brace and eventually the
+ // connection should time out.
+ test_socket_.reset(new test::TestServerUnixSocket(io_service_,
+ unixSocketFilePath(),
+ "{ \"command\": \"foo\""));
+ test_socket_->startTimer(TEST_TIMEOUT);
+
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Command to be sent to the server.
+ std::string command = "{ \"command\": \"list-commands\" }";
+
+ ClientConnection conn(io_service_);
+
+ // This boolean value will be set to true when the callback is invoked.
+ bool handler_invoked = false;
+ conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
+ ClientConnection::ControlCommand(command),
+ [this, &handler_invoked](const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& /*feed*/) {
+ // Indicate that the callback has been invoked to break the loop
+ // below.
+ handler_invoked = true;
+ ASSERT_TRUE(ec);
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ }, ClientConnection::Timeout(1000));
+
+ while (!handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+// This test checks that an error is returned when the client is unable
+// to connect to the server.
+TEST_F(ClientConnectionTest, connectionError) {
+ // Create the new connection but do not bind the server socket.
+ // The connection should be refused and an error returned.
+ ClientConnection conn(io_service_);
+
+ std::string command = "{ \"command\": \"list-commands\" }";
+
+ bool handler_invoked = false;
+ conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
+ ClientConnection::ControlCommand(command),
+ [this, &handler_invoked](const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& /*feed*/) {
+ handler_invoked = true;
+ ASSERT_TRUE(ec);
+ });
+
+ while (!handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/config/tests/command_mgr_unittests.cc b/src/lib/config/tests/command_mgr_unittests.cc
index 3f9a937e16..59682cd84f 100644
--- a/src/lib/config/tests/command_mgr_unittests.cc
+++ b/src/lib/config/tests/command_mgr_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,11 +6,21 @@
#include <gtest/gtest.h>
+#include <asiolink/io_service.h>
+#include <config/base_command_mgr.h>
#include <config/command_mgr.h>
+#include <config/hooked_command_mgr.h>
#include <cc/command_interpreter.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
+#include <hooks/library_handle.h>
+#include <string>
+#include <vector>
-using namespace isc::data;
+using namespace isc::asiolink;
using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
using namespace std;
// Test class for Command Manager
@@ -18,50 +28,135 @@ class CommandMgrTest : public ::testing::Test {
public:
/// Default constructor
- CommandMgrTest() {
- handler_name = "";
- handler_params = ElementPtr();
- handler_called = false;
+ CommandMgrTest()
+ : io_service_(new IOService()) {
+
+ CommandMgr::instance().setIOService(io_service_);
+
+ handler_name_ = "";
+ handler_params_ = ElementPtr();
+ handler_called_ = false;
CommandMgr::instance().deregisterAll();
CommandMgr::instance().closeCommandSocket();
+
+ resetCalloutIndicators();
}
/// Default destructor
- ~CommandMgrTest() {
+ virtual ~CommandMgrTest() {
CommandMgr::instance().deregisterAll();
CommandMgr::instance().closeCommandSocket();
+ resetCalloutIndicators();
+ }
+
+ /// @brief Returns socket path (using either hardcoded path or env variable)
+ /// @return path to the unix socket
+ std::string getSocketPath() {
+
+ std::string socket_path;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path = std::string(env) + "/test-socket";
+ } else {
+ socket_path = std::string(TEST_DATA_BUILDDIR) + "/test-socket";
+ }
+ return (socket_path);
+ }
+
+ /// @brief Resets indicators related to callout invocation.
+ ///
+ /// It also removes any registered callouts.
+ static void resetCalloutIndicators() {
+ callout_name_ = "";
+ callout_argument_names_.clear();
+
+ // Iterate over existing hook points and for each of them remove
+ // callouts registered.
+ std::vector<std::string> hooks = ServerHooks::getServerHooksPtr()->getHookNames();
+ for (auto h = hooks.cbegin(); h != hooks.cend(); ++h) {
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(*h);
+ }
}
/// @brief A simple command handler that always returns an eror
static ConstElementPtr my_handler(const std::string& name,
const ConstElementPtr& params) {
- handler_name = name;
- handler_params = params;
- handler_called = true;
+ handler_name_ = name;
+ handler_params_ = params;
+ handler_called_ = true;
return (createAnswer(123, "test error message"));
}
+ /// @brief A simple command handler used from within hook library.
+ ///
+ /// @param name Command name.
+ /// @param params Command arguments.
+ static ConstElementPtr my_hook_handler(const std::string& /*name*/,
+ const ConstElementPtr& /*params*/) {
+ return (createAnswer(234, "text generated by hook handler"));
+ }
+
+ /// @brief Test callback which stores callout name and passed arguments and
+ /// which handles the command.
+ ///
+ /// @param callout_handle Handle passed by the hooks framework.
+ /// @return Always 0.
+ static int
+ control_command_receive_handle_callout(CalloutHandle& callout_handle) {
+ callout_name_ = "control_command_receive_handle";
+
+ ConstElementPtr command;
+ callout_handle.getArgument("command", command);
+
+ ConstElementPtr arg;
+ std::string command_name = parseCommand(arg, command);
+
+ callout_handle.setArgument("response",
+ createAnswer(234, "text generated by hook handler"));
+
+ callout_argument_names_ = callout_handle.getArgumentNames();
+ // Sort arguments alphabetically, so as we can access them on
+ // expected positions and verify.
+ std::sort(callout_argument_names_.begin(), callout_argument_names_.end());
+ return (0);
+ }
+
+ /// @brief IO service used by these tests.
+ IOServicePtr io_service_;
+
/// @brief Name of the command (used in my_handler)
- static std::string handler_name;
+ static std::string handler_name_;
/// @brief Parameters passed to the handler (used in my_handler)
- static ConstElementPtr handler_params;
+ static ConstElementPtr handler_params_;
/// @brief Indicates whether my_handler was called
- static bool handler_called;
+ static bool handler_called_;
+
+ /// @brief Holds invoked callout name.
+ static std::string callout_name_;
+
+ /// @brief Holds a list of arguments passed to the callout.
+ static std::vector<std::string> callout_argument_names_;
};
/// Name passed to the handler (used in my_handler)
-std::string CommandMgrTest::handler_name("");
+std::string CommandMgrTest::handler_name_("");
/// Parameters passed to the handler (used in my_handler)
-ConstElementPtr CommandMgrTest::handler_params;
+ConstElementPtr CommandMgrTest::handler_params_;
/// Indicates whether my_handler was called
-bool CommandMgrTest::handler_called(false);
+bool CommandMgrTest::handler_called_(false);
+
+/// Holds invoked callout name.
+std::string CommandMgrTest::callout_name_("");
+
+/// Holds a list of arguments passed to the callout.
+std::vector<std::string> CommandMgrTest::callout_argument_names_;
// Test checks whether the internal command 'list-commands'
// is working properly.
@@ -92,7 +187,7 @@ TEST_F(CommandMgrTest, bogusCommand) {
ASSERT_TRUE(answer);
int status_code;
parseAnswer(status_code, answer);
- EXPECT_EQ(CONTROL_RESULT_ERROR, status_code);
+ EXPECT_EQ(CONTROL_RESULT_COMMAND_UNSUPPORTED, status_code);
}
// Test checks whether handlers installation is sanitized. In particular,
@@ -102,8 +197,8 @@ TEST_F(CommandMgrTest, handlerInstall) {
// Check that it's not allowed to install NULL pointer instead of a real
// command.
- EXPECT_THROW(CommandMgr::instance().registerCommand("my-command",
- NULL), InvalidCommandHandler);
+ EXPECT_THROW(CommandMgr::instance().registerCommand("my-command", 0),
+ InvalidCommandHandler);
// This registration should succeed.
EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
@@ -152,7 +247,7 @@ TEST_F(CommandMgrTest, listCommands) {
EXPECT_THROW(CommandMgr::instance().deregisterCommand("make-a-coffee"),
InvalidCommandName);
- // You can't unistall list-commands as it's the internal handler.
+ // You can't uninstall list-commands as it's the internal handler.
// It always must be there.
EXPECT_THROW(CommandMgr::instance().deregisterCommand("list-commands"),
InvalidCommandName);
@@ -185,7 +280,6 @@ TEST_F(CommandMgrTest, deregisterAll) {
// Test checks whether a command handler can be installed and then
// runs through processCommand to check that it's indeed called.
TEST_F(CommandMgrTest, processCommand) {
-
// Install my handler
EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
my_handler));
@@ -208,8 +302,136 @@ TEST_F(CommandMgrTest, processCommand) {
EXPECT_EQ(123, status_code);
// Check that the parameters passed are correct.
- EXPECT_EQ(true, handler_called);
- EXPECT_EQ("my-command", handler_name);
- ASSERT_TRUE(handler_params);
- EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", handler_params->str());
+ EXPECT_EQ(true, handler_called_);
+ EXPECT_EQ("my-command", handler_name_);
+ ASSERT_TRUE(handler_params_);
+ EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", handler_params_->str());
+
+ // Command handlers not installed so expecting that callouts weren't
+ // called.
+ EXPECT_TRUE(callout_name_.empty());
+}
+
+// Verify that processing a command can be delegated to a hook library.
+TEST_F(CommandMgrTest, delegateProcessCommand) {
+ // Register callout so as we can check that it is called before
+ // processing the command by the manager.
+ HooksManager::preCalloutsLibraryHandle().registerCommandCallout(
+ "my-command", control_command_receive_handle_callout);
+
+ // Install local handler
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Now tell CommandMgr to process a command 'my-command' with the
+ // specified parameter.
+ ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+ ConstElementPtr command = createCommand("my-command", my_params);
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ // Local handler shouldn't be called because the command is handled by the
+ // hook library.
+ ASSERT_FALSE(handler_called_);
+
+ // Returned status should be unique for the hook library.
+ ConstElementPtr answer_arg;
+ int status_code;
+ ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
+ EXPECT_EQ(234, status_code);
+
+ EXPECT_EQ("control_command_receive_handle", callout_name_);
+
+ // Check that the appropriate arguments have been set. Include the
+ // 'response' which should have been set by the callout.
+ ASSERT_EQ(2, callout_argument_names_.size());
+ EXPECT_EQ("command", callout_argument_names_[0]);
+ EXPECT_EQ("response", callout_argument_names_[1]);
+}
+
+// Verify that 'list-command' command returns combined list of supported
+// commands from hook library and from the Kea Command Manager.
+TEST_F(CommandMgrTest, delegateListCommands) {
+ // Register callout so as we can check that it is called before
+ // processing the command by the manager.
+ HooksManager::preCalloutsLibraryHandle().registerCommandCallout(
+ "my-command", control_command_receive_handle_callout);
+
+ // Create my-command-bis which is unique for the local Command Manager,
+ // i.e. not supported by the hook library. This command should also
+ // be returned as a result of processing 'list-commands'.
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command-bis",
+ my_handler));
+
+ // Process command. The command should be routed to the hook library
+ // and the hook library should return the commands it supports.
+ ConstElementPtr command = createCommand("list-commands");
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ ConstElementPtr answer_arg;
+ int status_code;
+ ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
+ EXPECT_EQ(0, status_code);
+
+ // The hook library supports: my-command and list-commands commands. The
+ // local Command Manager supports list-commands and my-command-bis. The
+ // combined list should include 3 unique commands.
+ const std::vector<ElementPtr>& commands_list = answer_arg->listValue();
+ ASSERT_EQ(3, commands_list.size());
+ std::vector<std::string> command_names_list;
+ for (auto cmd = commands_list.cbegin(); cmd != commands_list.cend();
+ ++cmd) {
+ command_names_list.push_back((*cmd)->stringValue());
+ }
+ std::sort(command_names_list.begin(), command_names_list.end());
+ EXPECT_EQ("list-commands", command_names_list[0]);
+ EXPECT_EQ("my-command", command_names_list[1]);
+ EXPECT_EQ("my-command-bis", command_names_list[2]);
+}
+
+// This test verifies that a Unix socket can be opened properly and that input
+// parameters (socket-type and socket-name) are verified.
+TEST_F(CommandMgrTest, unixCreate) {
+ // Null pointer is obviously a bad idea.
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(ConstElementPtr()),
+ isc::config::BadSocketInfo);
+
+ // So is passing no parameters.
+ ElementPtr socket_info = Element::createMap();
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ // We don't support ipx sockets
+ socket_info->set("socket-type", Element::create("ipx"));
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ socket_info->set("socket-type", Element::create("unix"));
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ socket_info->set("socket-name", Element::create(getSocketPath()));
+ EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
+ EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
+
+ // It should be possible to close the socket.
+ EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
+}
+
+// This test checks that when unix path is too long, the socket cannot be opened.
+TEST_F(CommandMgrTest, unixCreateTooLong) {
+ ElementPtr socket_info = Element::fromJSON("{ \"socket-type\": \"unix\","
+ "\"socket-name\": \"/tmp/toolongtoolongtoolongtoolongtoolongtoolong"
+ "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong"
+ "\" }");
+
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ SocketError);
}
diff --git a/src/lib/config/tests/command_socket_factory_unittests.cc b/src/lib/config/tests/command_socket_factory_unittests.cc
deleted file mode 100644
index 567d169364..0000000000
--- a/src/lib/config/tests/command_socket_factory_unittests.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#include <gtest/gtest.h>
-
-#include <cc/data.h>
-#include <config/command_mgr.h>
-#include <config/command_socket.h>
-#include <config/command_socket_factory.h>
-#include <cstdio>
-#include <cstdlib>
-
-using namespace isc::config;
-using namespace isc::data;
-
-// Test class for Command Manager
-class CommandSocketFactoryTest : public ::testing::Test {
-public:
-
- /// Default constructor
- CommandSocketFactoryTest()
- :SOCKET_NAME(getSocketPath()) {
-
- // Remove any stale socket files
- static_cast<void>(remove(SOCKET_NAME.c_str()));
- }
-
- /// Default destructor
- ~CommandSocketFactoryTest() {
-
- // Remove any stale socket files
- static_cast<void>(remove(SOCKET_NAME.c_str()));
- }
-
- /// @brief Returns socket path (using either hardcoded path or env variable)
- /// @return path to the unix socket
- std::string getSocketPath() {
-
- std::string socket_path;
- const char* env = getenv("KEA_SOCKET_TEST_DIR");
- if (env) {
- socket_path = std::string(env) + "/test-socket";
- } else {
- socket_path = std::string(TEST_DATA_BUILDDIR) + "/test-socket";
- }
- return (socket_path);
- }
-
- std::string SOCKET_NAME;
-};
-
-// This test verifies that a Unix socket can be opened properly and that input
-// parameters (socket-type and socket-name) are verified.
-TEST_F(CommandSocketFactoryTest, unixCreate) {
- // Null pointer is obviously a bad idea.
- EXPECT_THROW(CommandSocketFactory::create(ConstElementPtr()),
- isc::config::BadSocketInfo);
-
- // So is passing no parameters.
- ElementPtr socket_info = Element::createMap();
- EXPECT_THROW(CommandSocketFactory::create(socket_info),
- isc::config::BadSocketInfo);
-
- // We don't support ipx sockets
- socket_info->set("socket-type", Element::create("ipx"));
- EXPECT_THROW(CommandSocketFactory::create(socket_info),
- isc::config::BadSocketInfo);
-
- socket_info->set("socket-type", Element::create("unix"));
- EXPECT_THROW(CommandSocketFactory::create(socket_info),
- isc::config::BadSocketInfo);
-
- socket_info->set("socket-name", Element::create(SOCKET_NAME));
- CommandSocketPtr sock;
- EXPECT_NO_THROW(sock = CommandSocketFactory::create(socket_info));
- ASSERT_TRUE(sock);
- EXPECT_NE(-1, sock->getFD());
-
- // It should be possible to close the socket.
- EXPECT_NO_THROW(sock->close());
-}
-
-// This test checks that when unix path is too long, the socket cannot be opened.
-TEST_F(CommandSocketFactoryTest, unixCreateTooLong) {
- ElementPtr socket_info = Element::fromJSON("{ \"socket-type\": \"unix\","
- "\"socket-name\": \"/tmp/toolongtoolongtoolongtoolongtoolongtoolong"
- "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong"
- "\" }");
-
- EXPECT_THROW(CommandSocketFactory::create(socket_info), SocketError);
-}
diff --git a/src/lib/cryptolink/Makefile.am b/src/lib/cryptolink/Makefile.am
index 02ff2e95dd..2858aefb00 100644
--- a/src/lib/cryptolink/Makefile.am
+++ b/src/lib/cryptolink/Makefile.am
@@ -21,10 +21,11 @@ if HAVE_OPENSSL
libkea_cryptolink_la_SOURCES += openssl_link.cc
libkea_cryptolink_la_SOURCES += openssl_common.h
libkea_cryptolink_la_SOURCES += openssl_hash.cc
+libkea_cryptolink_la_SOURCES += openssl_compat.h
libkea_cryptolink_la_SOURCES += openssl_hmac.cc
endif
libkea_cryptolink_la_LDFLAGS = $(CRYPTO_LDFLAGS)
-libkea_cryptolink_la_LDFLAGS += -no-undefined -version-info 1:0:0
+libkea_cryptolink_la_LDFLAGS += -no-undefined -version-info 2:0:0
libkea_cryptolink_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
libkea_cryptolink_la_LIBADD += $(CRYPTO_LIBS)
diff --git a/src/lib/cryptolink/botan_common.h b/src/lib/cryptolink/botan_common.h
index 96ef1cd508..05cae30fbb 100644
--- a/src/lib/cryptolink/botan_common.h
+++ b/src/lib/cryptolink/botan_common.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12,7 +12,7 @@ namespace btn {
///
/// @param algorithm algorithm to be converted
/// @return static text representation of the algorithm name
-const char*
+const std::string
getHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm);
} // namespace btn
diff --git a/src/lib/cryptolink/botan_hash.cc b/src/lib/cryptolink/botan_hash.cc
index 3220ca1bee..0ed47e50b8 100644
--- a/src/lib/cryptolink/botan_hash.cc
+++ b/src/lib/cryptolink/botan_hash.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -16,7 +16,9 @@
#include <cryptolink/botan_common.h>
-#include <cstring>
+#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,0)
+#define secure_vector SecureVector
+#endif
namespace isc {
namespace cryptolink {
@@ -25,7 +27,7 @@ namespace cryptolink {
///
/// @param algorithm algorithm to be converted
/// @return text representation of the algorithm name
-const char*
+const std::string
btn::getHashAlgorithmName(HashAlgorithm algorithm) {
switch (algorithm) {
case isc::cryptolink::MD5:
@@ -60,13 +62,20 @@ public:
: hash_algorithm_(hash_algorithm), hash_() {
Botan::HashFunction* hash;
try {
- hash = Botan::get_hash(btn::getHashAlgorithmName(hash_algorithm));
+ const std::string& name =
+ btn::getHashAlgorithmName(hash_algorithm);
+#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,0)
+ hash = Botan::HashFunction::create(name).release();
+#else
+ hash = Botan::get_hash(name);
+#endif
} catch (const Botan::Algorithm_Not_Found&) {
isc_throw(isc::cryptolink::UnsupportedAlgorithm,
"Unknown hash algorithm: " <<
static_cast<int>(hash_algorithm));
} catch (const Botan::Exception& exc) {
- isc_throw(isc::cryptolink::LibraryError, exc.what());
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << exc.what());
}
hash_.reset(hash);
@@ -102,7 +111,8 @@ public:
try {
hash_->update(static_cast<const Botan::byte*>(data), len);
} catch (const Botan::Exception& exc) {
- isc_throw(isc::cryptolink::LibraryError, exc.what());
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << exc.what());
}
}
@@ -111,14 +121,15 @@ public:
/// See @ref isc::cryptolink::Hash::final() for details.
void final(isc::util::OutputBuffer& result, size_t len) {
try {
- Botan::SecureVector<Botan::byte> b_result(hash_->final());
+ Botan::secure_vector<Botan::byte> b_result(hash_->final());
if (len > b_result.size()) {
len = b_result.size();
}
- result.writeData(b_result.begin(), len);
+ result.writeData(&b_result[0], len);
} catch (const Botan::Exception& exc) {
- isc_throw(isc::cryptolink::LibraryError, exc.what());
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << exc.what());
}
}
@@ -127,14 +138,15 @@ public:
/// See @ref isc::cryptolink::Hash::final() for details.
void final(void* result, size_t len) {
try {
- Botan::SecureVector<Botan::byte> b_result(hash_->final());
+ Botan::secure_vector<Botan::byte> b_result(hash_->final());
size_t output_size = getOutputLength();
if (output_size > len) {
output_size = len;
}
- std::memcpy(result, b_result.begin(), output_size);
+ std::memcpy(result, &b_result[0], output_size);
} catch (const Botan::Exception& exc) {
- isc_throw(isc::cryptolink::LibraryError, exc.what());
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << exc.what());
}
}
@@ -143,14 +155,14 @@ public:
/// See @ref isc::cryptolink::Hash::final() for details.
std::vector<uint8_t> final(size_t len) {
try {
- Botan::SecureVector<Botan::byte> b_result(hash_->final());
+ Botan::secure_vector<Botan::byte> b_result(hash_->final());
if (len > b_result.size()) {
- return (std::vector<uint8_t>(b_result.begin(), b_result.end()));
- } else {
- return (std::vector<uint8_t>(b_result.begin(), &b_result[len]));
+ len = b_result.size();
}
+ return (std::vector<uint8_t>(&b_result[0], &b_result[len]));
} catch (const Botan::Exception& exc) {
- isc_throw(isc::cryptolink::LibraryError, exc.what());
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << exc.what());
}
}
diff --git a/src/lib/cryptolink/botan_hmac.cc b/src/lib/cryptolink/botan_hmac.cc
index 454673de8c..6c34527a3b 100644
--- a/src/lib/cryptolink/botan_hmac.cc
+++ b/src/lib/cryptolink/botan_hmac.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -17,7 +17,9 @@
#include <cryptolink/botan_common.h>
-#include <cstring>
+#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,0)
+#define secure_vector SecureVector
+#endif
namespace isc {
namespace cryptolink {
@@ -38,13 +40,25 @@ public:
: hash_algorithm_(hash_algorithm), hmac_() {
Botan::HashFunction* hash;
try {
- hash = Botan::get_hash(btn::getHashAlgorithmName(hash_algorithm));
+ const std::string& name =
+ btn::getHashAlgorithmName(hash_algorithm);
+#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,0)
+ std::unique_ptr<Botan::HashFunction> hash_ptr =
+ Botan::HashFunction::create(name);
+ if (hash_ptr) {
+ hash = hash_ptr.release();
+ } else {
+ throw Botan::Algorithm_Not_Found(name);
+ }
+#else
+ hash = Botan::get_hash(name);
+#endif
} catch (const Botan::Algorithm_Not_Found&) {
isc_throw(UnsupportedAlgorithm,
"Unknown hash algorithm: " <<
static_cast<int>(hash_algorithm));
} catch (const Botan::Exception& exc) {
- isc_throw(LibraryError, exc.what());
+ isc_throw(LibraryError, "Botan error: " << exc.what());
}
hmac_.reset(new Botan::HMAC(hash));
@@ -64,10 +78,10 @@ public:
size_t block_length = 0;
#endif
if (secret_len > block_length) {
- Botan::SecureVector<Botan::byte> hashed_key =
+ Botan::secure_vector<Botan::byte> hashed_key =
hash->process(static_cast<const Botan::byte*>(secret),
secret_len);
- hmac_->set_key(hashed_key.begin(), hashed_key.size());
+ hmac_->set_key(&hashed_key[0], hashed_key.size());
} else {
// Botan 1.8 considers len 0 a bad key. 1.9 does not,
// but we won't accept it anyway, and fail early
@@ -80,7 +94,7 @@ public:
} catch (const Botan::Invalid_Key_Length& ikl) {
isc_throw(BadKey, ikl.what());
} catch (const Botan::Exception& exc) {
- isc_throw(LibraryError, exc.what());
+ isc_throw(LibraryError, "Botan error: " << exc.what());
}
}
@@ -115,7 +129,7 @@ public:
try {
hmac_->update(static_cast<const Botan::byte*>(data), len);
} catch (const Botan::Exception& exc) {
- isc_throw(LibraryError, exc.what());
+ isc_throw(LibraryError, "Botan error: " << exc.what());
}
}
@@ -124,14 +138,14 @@ public:
/// See @ref isc::cryptolink::HMAC::sign() for details.
void sign(isc::util::OutputBuffer& result, size_t len) {
try {
- Botan::SecureVector<Botan::byte> b_result(hmac_->final());
+ Botan::secure_vector<Botan::byte> b_result(hmac_->final());
if (len > b_result.size()) {
len = b_result.size();
}
- result.writeData(b_result.begin(), len);
+ result.writeData(&b_result[0], len);
} catch (const Botan::Exception& exc) {
- isc_throw(LibraryError, exc.what());
+ isc_throw(LibraryError, "Botan error: " << exc.what());
}
}
@@ -140,14 +154,14 @@ public:
/// See @ref isc::cryptolink::HMAC::sign() for details.
void sign(void* result, size_t len) {
try {
- Botan::SecureVector<Botan::byte> b_result(hmac_->final());
+ Botan::secure_vector<Botan::byte> b_result(hmac_->final());
size_t output_size = getOutputLength();
if (output_size > len) {
output_size = len;
}
- std::memcpy(result, b_result.begin(), output_size);
+ std::memcpy(result, &b_result[0], output_size);
} catch (const Botan::Exception& exc) {
- isc_throw(LibraryError, exc.what());
+ isc_throw(LibraryError, "Botan error: " << exc.what());
}
}
@@ -156,14 +170,13 @@ public:
/// See @ref isc::cryptolink::HMAC::sign() for details.
std::vector<uint8_t> sign(size_t len) {
try {
- Botan::SecureVector<Botan::byte> b_result(hmac_->final());
+ Botan::secure_vector<Botan::byte> b_result(hmac_->final());
if (len > b_result.size()) {
- return (std::vector<uint8_t>(b_result.begin(), b_result.end()));
- } else {
- return (std::vector<uint8_t>(b_result.begin(), &b_result[len]));
+ len = b_result.size();
}
+ return (std::vector<uint8_t>(&b_result[0], &b_result[len]));
} catch (const Botan::Exception& exc) {
- isc_throw(LibraryError, exc.what());
+ isc_throw(LibraryError, "Botan error: " << exc.what());
}
}
@@ -190,7 +203,7 @@ public:
static_cast<const unsigned char*>(sig),
len));
} catch (const Botan::Exception& exc) {
- isc_throw(LibraryError, exc.what());
+ isc_throw(LibraryError, "Botan error: " << exc.what());
}
}
@@ -202,7 +215,7 @@ private:
boost::scoped_ptr<Botan::HMAC> hmac_;
/// @brief The digest cache for multiple verify
- Botan::SecureVector<Botan::byte> digest_;
+ Botan::secure_vector<Botan::byte> digest_;
};
HMAC::HMAC(const void* secret, size_t secret_length,
diff --git a/src/lib/cryptolink/botan_link.cc b/src/lib/cryptolink/botan_link.cc
index c8f4a95c38..57ff19dbe7 100644
--- a/src/lib/cryptolink/botan_link.cc
+++ b/src/lib/cryptolink/botan_link.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,7 +8,10 @@
#include <cryptolink/crypto_hash.h>
#include <cryptolink/crypto_hmac.h>
+#define BOTAN_NO_DEPRECATED_WARNINGS
+
#include <botan/botan.h>
+#include <botan/init.h>
namespace isc {
namespace cryptolink {
@@ -30,7 +33,7 @@ CryptoLink::initialize() {
try {
c.impl_ = new CryptoLinkImpl();
} catch (const Botan::Exception& ex) {
- isc_throw(InitializationError, ex.what());
+ isc_throw(InitializationError, "Botan error: " << ex.what());
}
}
}
diff --git a/src/lib/cryptolink/crypto_hash.h b/src/lib/cryptolink/crypto_hash.h
index e5d94ed70b..f5ea3fade5 100644
--- a/src/lib/cryptolink/crypto_hash.h
+++ b/src/lib/cryptolink/crypto_hash.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/lib/cryptolink/crypto_hmac.h b/src/lib/cryptolink/crypto_hmac.h
index 709971ae2e..5c7bffee61 100644
--- a/src/lib/cryptolink/crypto_hmac.h
+++ b/src/lib/cryptolink/crypto_hmac.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/lib/cryptolink/openssl_compat.h b/src/lib/cryptolink/openssl_compat.h
new file mode 100644
index 0000000000..c24ec3c9fb
--- /dev/null
+++ b/src/lib/cryptolink/openssl_compat.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <openssl/opensslv.h>
+
+#if defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x10100000L)
+
+// This file is included by hash and hmac codes so KEA_H* macros
+// avoid to define unused inlines.
+
+#ifdef KEA_HASH
+
+// EVP_MD_CTX_new() is EVP_MD_CTX_create() in OpenSSL < 1.1
+
+inline EVP_MD_CTX* EVP_MD_CTX_new() {
+ return (EVP_MD_CTX_create());
+}
+
+// EVP_MD_CTX_free(ctx) is EVP_MD_CTX_destroy(ctx) in OpenSSL < 1.1
+
+inline void EVP_MD_CTX_free(EVP_MD_CTX* ctx) {
+ EVP_MD_CTX_destroy(ctx);
+}
+
+#endif
+
+#ifdef KEA_HMAC
+
+// HMAC_CTX_new() implementation for OpenSSL < 1.1
+
+inline HMAC_CTX* HMAC_CTX_new() {
+ HMAC_CTX* ctx = static_cast<HMAC_CTX*>(OPENSSL_malloc(sizeof(HMAC_CTX)));
+ if (ctx != 0) {
+ HMAC_CTX_init(ctx);
+ }
+ return (ctx);
+}
+
+// HMAC_CTX_free() implementation for OpenSSL < 1.1
+
+inline void HMAC_CTX_free(HMAC_CTX* ctx) {
+ if (ctx != 0) {
+ HMAC_CTX_cleanup(ctx);
+ OPENSSL_free(ctx);
+ }
+}
+
+#endif
+
+#endif
diff --git a/src/lib/cryptolink/openssl_hash.cc b/src/lib/cryptolink/openssl_hash.cc
index e1f5aa8047..4d68a32033 100644
--- a/src/lib/cryptolink/openssl_hash.cc
+++ b/src/lib/cryptolink/openssl_hash.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12,6 +12,8 @@
#include <openssl/evp.h>
#include <cryptolink/openssl_common.h>
+#define KEA_HASH
+#include <cryptolink/openssl_compat.h>
#include <cstring>
@@ -55,7 +57,7 @@ public:
///
/// @param hash_algorithm The hash algorithm
explicit HashImpl(const HashAlgorithm hash_algorithm)
- : hash_algorithm_(hash_algorithm), md_() {
+ : hash_algorithm_(hash_algorithm), md_(0) {
const EVP_MD* algo = ossl::getHashAlgorithm(hash_algorithm);
if (algo == 0) {
isc_throw(isc::cryptolink::UnsupportedAlgorithm,
@@ -63,18 +65,21 @@ public:
static_cast<int>(hash_algorithm));
}
- md_.reset(new EVP_MD_CTX);
-
- EVP_MD_CTX_init(md_.get());
+ md_ = EVP_MD_CTX_new();
+ if (md_ == 0) {
+ isc_throw(isc::cryptolink::LibraryError,
+ "OpenSSL EVP_MD_CTX_new() failed");
+ }
- EVP_DigestInit_ex(md_.get(), algo, NULL);
+ EVP_DigestInit_ex(md_, algo, NULL);
}
/// @brief Destructor
~HashImpl() {
if (md_) {
- EVP_MD_CTX_cleanup(md_.get());
+ EVP_MD_CTX_free(md_);
}
+ md_ = 0;
}
/// @brief Returns the HashAlgorithm of the object
@@ -86,14 +91,14 @@ public:
///
/// @return output size of the digest
size_t getOutputLength() const {
- return (EVP_MD_CTX_size(md_.get()));
+ return (EVP_MD_CTX_size(md_));
}
/// @brief Adds data to the digest
///
/// See @ref isc::cryptolink::Hash::update() for details.
void update(const void* data, const size_t len) {
- EVP_DigestUpdate(md_.get(), data, len);
+ EVP_DigestUpdate(md_, data, len);
}
/// @brief Calculate the final digest
@@ -102,7 +107,7 @@ public:
void final(isc::util::OutputBuffer& result, size_t len) {
size_t size = getOutputLength();
std::vector<unsigned char> digest(size);
- EVP_DigestFinal_ex(md_.get(), &digest[0], NULL);
+ EVP_DigestFinal_ex(md_, &digest[0], NULL);
if (len > size) {
len = size;
}
@@ -115,7 +120,7 @@ public:
void final(void* result, size_t len) {
size_t size = getOutputLength();
std::vector<unsigned char> digest(size);
- EVP_DigestFinal_ex(md_.get(), &digest[0], NULL);
+ EVP_DigestFinal_ex(md_, &digest[0], NULL);
if (len > size) {
len = size;
}
@@ -128,7 +133,7 @@ public:
std::vector<uint8_t> final(size_t len) {
size_t size = getOutputLength();
std::vector<unsigned char> digest(size);
- EVP_DigestFinal_ex(md_.get(), &digest[0], NULL);
+ EVP_DigestFinal_ex(md_, &digest[0], NULL);
if (len < size) {
digest.resize(len);
}
@@ -139,8 +144,8 @@ private:
/// @brief The hash algorithm
HashAlgorithm hash_algorithm_;
- /// @brief The protected pointer to the OpenSSL EVP_MD_CTX structure
- boost::scoped_ptr<EVP_MD_CTX> md_;
+ /// @brief The pointer to the OpenSSL EVP_MD_CTX structure
+ EVP_MD_CTX* md_;
};
Hash::Hash(const HashAlgorithm hash_algorithm)
diff --git a/src/lib/cryptolink/openssl_hmac.cc b/src/lib/cryptolink/openssl_hmac.cc
index d73b373f52..5529858371 100644
--- a/src/lib/cryptolink/openssl_hmac.cc
+++ b/src/lib/cryptolink/openssl_hmac.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12,6 +12,8 @@
#include <openssl/hmac.h>
#include <cryptolink/openssl_common.h>
+#define KEA_HMAC
+#include <cryptolink/openssl_compat.h>
#include <cstring>
@@ -42,21 +44,24 @@ public:
isc_throw(BadKey, "Bad HMAC secret length: 0");
}
- md_.reset(new HMAC_CTX);
- HMAC_CTX_init(md_.get());
+ md_ = HMAC_CTX_new();
+ if (md_ == 0) {
+ isc_throw(LibraryError, "OpenSSL HMAC_CTX_new() failed");
+ }
- if (!HMAC_Init_ex(md_.get(), secret,
+ if (!HMAC_Init_ex(md_, secret,
static_cast<int>(secret_len),
algo, NULL)) {
- isc_throw(LibraryError, "HMAC_Init_ex");
+ isc_throw(LibraryError, "OpenSSL HMAC_Init_ex() failed");
}
}
/// @brief Destructor
~HMACImpl() {
if (md_) {
- HMAC_CTX_cleanup(md_.get());
+ HMAC_CTX_free(md_);
}
+ md_ = 0;
}
/// @brief Returns the HashAlgorithm of the object
@@ -68,9 +73,9 @@ public:
///
/// @return output size of the digest
size_t getOutputLength() const {
- int size = HMAC_size(md_.get());
+ int size = HMAC_size(md_);
if (size < 0) {
- isc_throw(LibraryError, "HMAC_size");
+ isc_throw(LibraryError, "OpenSSL HMAC_size() failed");
}
return (static_cast<size_t>(size));
}
@@ -79,10 +84,10 @@ public:
///
/// See @ref isc::cryptolink::HMAC::update() for details.
void update(const void* data, const size_t len) {
- if (!HMAC_Update(md_.get(),
+ if (!HMAC_Update(md_,
static_cast<const unsigned char*>(data),
len)) {
- isc_throw(LibraryError, "HMAC_Update");
+ isc_throw(LibraryError, "OpenSSLHMAC_Update() failed");
}
}
@@ -92,8 +97,8 @@ public:
void sign(isc::util::OutputBuffer& result, size_t len) {
size_t size = getOutputLength();
ossl::SecBuf<unsigned char> digest(size);
- if (!HMAC_Final(md_.get(), &digest[0], NULL)) {
- isc_throw(LibraryError, "HMAC_Final");
+ if (!HMAC_Final(md_, &digest[0], NULL)) {
+ isc_throw(LibraryError, "OpenSSL HMAC_Final() failed");
}
if (len > size) {
len = size;
@@ -107,8 +112,8 @@ public:
void sign(void* result, size_t len) {
size_t size = getOutputLength();
ossl::SecBuf<unsigned char> digest(size);
- if (!HMAC_Final(md_.get(), &digest[0], NULL)) {
- isc_throw(LibraryError, "HMAC_Final");
+ if (!HMAC_Final(md_, &digest[0], NULL)) {
+ isc_throw(LibraryError, "OpenSSL HMAC_Final() failed");
}
if (len > size) {
len = size;
@@ -122,8 +127,8 @@ public:
std::vector<uint8_t> sign(size_t len) {
size_t size = getOutputLength();
ossl::SecBuf<unsigned char> digest(size);
- if (!HMAC_Final(md_.get(), &digest[0], NULL)) {
- isc_throw(LibraryError, "HMAC_Final");
+ if (!HMAC_Final(md_, &digest[0], NULL)) {
+ isc_throw(LibraryError, "OpenSSL HMAC_Final() failed");
}
if (len < size) {
digest.resize(len);
@@ -141,17 +146,20 @@ public:
return (false);
}
// Get the digest from a copy of the context
- HMAC_CTX tmp;
- HMAC_CTX_init(&tmp);
- if (!HMAC_CTX_copy(&tmp, md_.get())) {
- isc_throw(LibraryError, "HMAC_CTX_copy");
+ HMAC_CTX* tmp = HMAC_CTX_new();
+ if (tmp == 0) {
+ isc_throw(LibraryError, "OpenSSL HMAC_CTX_new() failed");
+ }
+ if (!HMAC_CTX_copy(tmp, md_)) {
+ HMAC_CTX_free(tmp);
+ isc_throw(LibraryError, "OpenSSL HMAC_CTX_copy() failed");
}
ossl::SecBuf<unsigned char> digest(size);
- if (!HMAC_Final(&tmp, &digest[0], NULL)) {
- HMAC_CTX_cleanup(&tmp);
- isc_throw(LibraryError, "HMAC_Final");
+ if (!HMAC_Final(tmp, &digest[0], NULL)) {
+ HMAC_CTX_free(tmp);
+ isc_throw(LibraryError, "OpenSSL HMAC_Final() failed");
}
- HMAC_CTX_cleanup(&tmp);
+ HMAC_CTX_free(tmp);
if (len > size) {
len = size;
}
@@ -163,7 +171,7 @@ private:
HashAlgorithm hash_algorithm_;
/// @brief The protected pointer to the OpenSSL HMAC_CTX structure
- boost::scoped_ptr<HMAC_CTX> md_;
+ HMAC_CTX* md_;
};
HMAC::HMAC(const void* secret, size_t secret_length,
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index b3cdbc1588..12c3ebf844 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -72,7 +72,7 @@ libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink
libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
libkea_dhcp___la_LIBADD += $(CRYPTO_LIBS)
-libkea_dhcp___la_LDFLAGS = -no-undefined -version-info 4:1:0
+libkea_dhcp___la_LDFLAGS = -no-undefined -version-info 5:0:0
libkea_dhcp___la_LDFLAGS += $(CRYPTO_LDFLAGS)
EXTRA_DIST = README libdhcp++.dox
@@ -99,6 +99,7 @@ libkea_dhcp___include_HEADERS = \
option6_ia.h \
option6_iaaddr.h \
option6_iaprefix.h \
+ option6_pdexclude.h \
option6_status_code.h \
option.h \
option_custom.h \
diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h
index 65f636f96c..21409b282b 100644
--- a/src/lib/dhcp/dhcp6.h
+++ b/src/lib/dhcp/dhcp6.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2006-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2006-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -104,14 +104,6 @@
//#define D6O_V6_PCP_SERVER 86 /* RFC7291 */
#define D6O_DHCPV4_MSG 87 /* RFC7341 */
#define D6O_DHCPV4_O_DHCPV6_SERVER 88 /* RFC7341 */
-#define D6O_S46_RULE 89 /* RFC7598 */
-#define D6O_S46_BR 90 /* RFC7598 */
-#define D6O_S46_DMR 91 /* RFC7598 */
-#define D6O_S46_V4V6BIND 92 /* RFC7598 */
-#define D6O_S46_PORTPARAMS 93 /* RFC7598 */
-#define D6O_S46_CONT_MAPE 94 /* RFC7598 */
-#define D6O_S46_CONT_MAPT 95 /* RFC7598 */
-#define D6O_S46_CONT_LW 96 /* RFC7598 */
//#define D6O_4RD 97 /* RFC7600 */
//#define D6O_4RD_MAP_RULE 98 /* RFC7600 */
//#define D6O_4RD_NON_MAP_RULE 99 /* RFC7600 */
diff --git a/src/lib/dhcp/docsis3_option_defs.h b/src/lib/dhcp/docsis3_option_defs.h
index 7d068407b6..879ffd56ec 100644
--- a/src/lib/dhcp/docsis3_option_defs.h
+++ b/src/lib/dhcp/docsis3_option_defs.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -54,7 +54,7 @@ const OptionDefParams DOCSIS3_V6_DEFS[] = {
{ "device-id", DOCSIS3_V6_DEVICE_ID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "time-offset", DOCSIS3_V6_TIME_OFFSET, OPT_INT32_TYPE, false, NO_RECORD_DEF, "" },
{ "cmts-cm-mac", DOCSIS3_V6_CMTS_CM_MAC, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" }
- // @todo add definitions for all remaning options.
+ // @todo add definitions for all remaining options.
};
/// Number of option definitions defined.
diff --git a/src/lib/dhcp/duid_factory.cc b/src/lib/dhcp/duid_factory.cc
index b5563f07c6..c19fcd4462 100644
--- a/src/lib/dhcp/duid_factory.cc
+++ b/src/lib/dhcp/duid_factory.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -96,7 +96,7 @@ DUIDFactory::createLLT(const uint16_t htype, const uint32_t time_in,
}
} else if (htype_out == 0) {
- // If link layer type unspecified and link layer adddress
+ // If link layer type unspecified and link layer address
// is specified, use current type or HTYPE_ETHER.
htype_out = ((htype_current != 0) ? htype_current :
static_cast<uint16_t>(HTYPE_ETHER));
@@ -221,7 +221,7 @@ DUIDFactory::createLL(const uint16_t htype,
}
} else if (htype_out == 0) {
- // If link layer type unspecified and link layer adddress
+ // If link layer type unspecified and link layer address
// is specified, use current type or HTYPE_ETHER.
htype_out = ((htype_current != 0) ? htype_current :
static_cast<uint16_t>(HTYPE_ETHER));
@@ -285,6 +285,12 @@ DUIDFactory::createLinkLayerId(std::vector<uint8_t>& identifier,
// Assign link layer address and type.
identifier.assign(iface->getMac(), iface->getMac() + iface->getMacLen());
htype = iface->getHWType();
+
+ // If it looks like an Ethernet interface we should be happy
+ if ((htype == static_cast<uint16_t>(HTYPE_ETHER)) &&
+ (iface->getMacLen() == 6)) {
+ break;
+ }
}
// We failed to find an interface which link layer address could be
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index 0632bac55f..92f90e9796 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -68,7 +68,7 @@ Iface::closeSockets() {
void
Iface::closeSockets(const uint16_t family) {
- // Check that the correect 'family' value has been specified.
+ // Check that the correct 'family' value has been specified.
// The possible values are AF_INET or AF_INET6. Note that, in
// the current code they are used to differentiate that the
// socket is used to transmit IPv4 or IPv6 traffic. However,
@@ -937,7 +937,7 @@ Pkt4Ptr IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
} else if (result < 0) {
// In most cases we would like to know whether select() returned
// an error because of a signal being received or for some other
- // reasaon. This is because DHCP servers use signals to trigger
+ // reason. This is because DHCP servers use signals to trigger
// certain actions, like reconfiguration or graceful shutdown.
// By catching a dedicated exception the caller will know if the
// error returned by the function is due to the reception of the
@@ -1047,7 +1047,7 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
} else if (result < 0) {
// In most cases we would like to know whether select() returned
// an error because of a signal being received or for some other
- // reasaon. This is because DHCP servers use signals to trigger
+ // reason. This is because DHCP servers use signals to trigger
// certain actions, like reconfiguration or graceful shutdown.
// By catching a dedicated exception the caller will know if the
// error returned by the function is due to the reception of the
@@ -1161,7 +1161,7 @@ IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
}
const Iface::SocketCollection& socket_collection = iface->getSockets();
- // A candidate being an end of the iterator marks that it is a begining of
+ // A candidate being an end of the iterator marks that it is a beginning of
// the socket search and that the candidate needs to be set to the first
// socket found.
Iface::SocketCollection::const_iterator candidate = socket_collection.end();
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
index 8aaf9c9248..7aea6e43cd 100644
--- a/src/lib/dhcp/iface_mgr.h
+++ b/src/lib/dhcp/iface_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -58,7 +58,7 @@ public:
isc::Exception(file, line, what) { };
};
-/// @brief IfaceMgr exception thrown thrown when error occured during
+/// @brief IfaceMgr exception thrown thrown when error occurred during
/// reading data from socket.
class SocketReadError : public Exception {
public:
@@ -66,8 +66,8 @@ public:
isc::Exception(file, line, what) { };
};
-/// @brief IfaceMgr exception thrown thrown when error occured during
-/// sedning data through socket.
+/// @brief IfaceMgr exception thrown thrown when error occurred during
+/// sending data through socket.
class SocketWriteError : public Exception {
public:
SocketWriteError(const char* file, size_t line, const char* what) :
@@ -150,7 +150,7 @@ struct SocketInfo {
/// may require use of fixed-size buffers. The size of such a buffer is
/// returned by the OS kernel when the socket is opened. Hence, it is
/// convenient to allocate the buffer when the socket is being opened and
-/// utilze it throughout the lifetime of the socket.
+/// utilize it throughout the lifetime of the socket.
///
/// In order to avoid potentially expensive copies of the @c Iface objects
/// holding pre-allocated buffers and multiple containers, this class is
@@ -303,7 +303,7 @@ public:
/// @brief Check if the interface has the specified address assigned.
///
/// @param address Address to be checked.
- /// @return true if address is assigned to the intefrace, false otherwise.
+ /// @return true if address is assigned to the interface, false otherwise.
bool hasAddress(const isc::asiolink::IOAddress& address) const;
/// @brief Adds an address to an interface.
@@ -438,7 +438,7 @@ protected:
/// Network interface name.
std::string name_;
- /// Interface index (a value that uniquely indentifies an interface).
+ /// Interface index (a value that uniquely identifies an interface).
int ifindex_;
/// List of assigned addresses.
@@ -724,7 +724,7 @@ public:
/// (in microseconds)
///
/// @throw isc::BadValue if timeout_usec is greater than one million
- /// @throw isc::dhcp::SocketReadError if error occured when receiving a
+ /// @throw isc::dhcp::SocketReadError if error occurred when receiving a
/// packet.
/// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is
/// interrupted by a signal.
@@ -746,7 +746,7 @@ public:
/// (in microseconds)
///
/// @throw isc::BadValue if timeout_usec is greater than one million
- /// @throw isc::dhcp::SocketReadError if error occured when receiving a
+ /// @throw isc::dhcp::SocketReadError if error occurred when receiving a
/// packet.
/// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is
/// interrupted by a signal.
@@ -877,7 +877,7 @@ public:
/// @throw SocketOpenFailure if tried and failed to open socket.
/// @return true if any sockets were open
bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT,
- IfaceMgrErrorMsgCallback error_handler = NULL);
+ IfaceMgrErrorMsgCallback error_handler = 0);
/// @brief Opens IPv4 sockets on detected interfaces.
///
@@ -946,7 +946,7 @@ public:
/// @return true if any sockets were open
bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT,
const bool use_bcast = true,
- IfaceMgrErrorMsgCallback error_handler = NULL);
+ IfaceMgrErrorMsgCallback error_handler = 0);
/// @brief Closes all open sockets.
/// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes.
@@ -1010,7 +1010,7 @@ public:
/// @throw PacketFilterChangeDenied if there are open IPv4 sockets.
void setPacketFilter(const PktFilterPtr& packet_filter);
- /// @brief Set packet filter object to handle sending and receving DHCPv6
+ /// @brief Set packet filter object to handle sending and receiving DHCPv6
/// messages.
///
/// Packet filter objects provide means for the @c IfaceMgr to open sockets
@@ -1043,7 +1043,7 @@ public:
/// implementation that supports this feature on a particular OS.
/// If there isn't, the PktFilterInet object will be set. If the
/// argument is set to 'false', PktFilterInet object instance will
- /// be set as the Packet Filter regrdaless of the OS type.
+ /// be set as the Packet Filter regardless of the OS type.
///
/// @param direct_response_desired specifies whether the Packet Filter
/// object being set should support direct traffic to the host
@@ -1194,7 +1194,7 @@ private:
bool openMulticastSocket(Iface& iface,
const isc::asiolink::IOAddress& addr,
const uint16_t port,
- IfaceMgrErrorMsgCallback error_handler = NULL);
+ IfaceMgrErrorMsgCallback error_handler = 0);
/// Holds instance of a class derived from PktFilter, used by the
/// IfaceMgr to open sockets and send/receive packets through these
diff --git a/src/lib/dhcp/iface_mgr_error_handler.h b/src/lib/dhcp/iface_mgr_error_handler.h
index 0956bf69ac..b0ea26b32c 100644
--- a/src/lib/dhcp/iface_mgr_error_handler.h
+++ b/src/lib/dhcp/iface_mgr_error_handler.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -23,7 +23,7 @@
/// cases it is expected that the exception is thrown instead. A possible
/// solution would be to enclose this conditional behavior in a function.
/// However, despite the hate for macros, the macro seems to be a bit
-/// better solution in this case as it allows to convenietly pass an
+/// better solution in this case as it allows to conveniently pass an
/// error string in a stream (not as a string).
///
/// @param ex_type Exception to be thrown if error_handler is NULL.
diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc
index 495742527a..36d263d151 100644
--- a/src/lib/dhcp/iface_mgr_linux.cc
+++ b/src/lib/dhcp/iface_mgr_linux.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -487,7 +487,7 @@ void IfaceMgr::detectIfaces() {
}
nl.ipaddrs_get(*iface, addr_info);
- ifaces_.push_back(iface);
+ addInterface(iface);
}
nl.release_list(link_info);
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index 033a66e2f6..df25719d9e 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -631,7 +631,7 @@ size_t LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffe
isc::dhcp::OptionCollection& options) {
size_t offset = 0;
- // Get the list of stdandard option definitions.
+ // Get the list of standard option definitions.
const OptionDefContainerPtr& option_defs = LibDHCP::getVendorOption4Defs(vendor_id);
// Get the search index #1. It allows to search for option definitions
// using option code.
diff --git a/src/lib/dhcp/libdhcp++.dox b/src/lib/dhcp/libdhcp++.dox
index d79b7514f3..08f279e6f1 100644
--- a/src/lib/dhcp/libdhcp++.dox
+++ b/src/lib/dhcp/libdhcp++.dox
@@ -1,11 +1,11 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/**
-@page libdhcp libdhcp++
+@page libdhcp libkea-dhcp++ - Low Level DHCP Library
@section libdhcpIntro Libdhcp++ Library Introduction
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index 38009d44b7..b5791dcaf4 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -298,7 +298,7 @@ public:
/// @brief Removes runtime option definitions.
static void clearRuntimeOptionDefs();
- /// @brief Reverts uncommited changes to runtime option definitions.
+ /// @brief Reverts uncommitted changes to runtime option definitions.
static void revertRuntimeOptionDefs();
/// @brief Commits runtime option definitions.
@@ -368,7 +368,7 @@ private:
/// Container for v6 vendor option definitions
static VendorOptionDefContainers vendor6_defs_;
- /// Container for additional option defnitions created in runtime.
+ /// Container for additional option definitions created in runtime.
static util::StagedValue<OptionDefSpaceContainer> runtime_option_defs_;
};
diff --git a/src/lib/dhcp/opaque_data_tuple.h b/src/lib/dhcp/opaque_data_tuple.h
index f055fab716..b20ab6b1b5 100644
--- a/src/lib/dhcp/opaque_data_tuple.h
+++ b/src/lib/dhcp/opaque_data_tuple.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -114,7 +114,7 @@ public:
/// @brief Assigns data to the tuple.
///
/// This function replaces existing data in the tuple with the new data.
- /// If the speficified buffer length is greater than the size of the buffer,
+ /// If the specified buffer length is greater than the size of the buffer,
/// the behavior of this function is undefined.
/// @param data Iterator pointing to the beginning of the buffer being
/// assigned. The source buffer may be an STL object or an array of
@@ -179,7 +179,7 @@ public:
/// and writes it to the specified buffer. The new are appended to the
/// buffer, so as data existing in the buffer is preserved.
///
- /// The tuple is considered malformed if one of the follwing occurs:
+ /// The tuple is considered malformed if one of the following occurs:
/// - the size of the data is 0 (tuple is empty),
/// - the size of the data is greater than 255 and the size of the length
/// field is 1 byte (see @c LengthFieldType).
diff --git a/src/lib/dhcp/option4_addrlst.h b/src/lib/dhcp/option4_addrlst.h
index 24ff1288c3..8eac69ec88 100644
--- a/src/lib/dhcp/option4_addrlst.h
+++ b/src/lib/dhcp/option4_addrlst.h
@@ -121,7 +121,7 @@ public:
/// address to existing list or setAddresses() if you want to
/// set the whole list at once.
///
- /// Passed address must be IPv4 address. Otherwire BadValue exception
+ /// Passed address must be IPv4 address. Otherwise BadValue exception
/// will be thrown.
///
/// @param addrs address collection to be set
@@ -135,7 +135,7 @@ public:
/// address to existing list or setAddresses() if you want to
/// set the whole list at once.
///
- /// Passed address must be IPv4 address. Otherwire BadValue exception
+ /// Passed address must be IPv4 address. Otherwise BadValue exception
/// will be thrown.
///
/// @param addr an address that is going to be set as 1-element address list
@@ -147,10 +147,10 @@ public:
/// define only a single address or setAddresses() if you want to
/// set the whole list at once.
///
- /// Passed address must be IPv4 address. Otherwire BadValue exception
+ /// Passed address must be IPv4 address. Otherwise BadValue exception
/// will be thrown.
///
- /// @param addr an address thait is going to be added to existing list
+ /// @param addr an address that is going to be added to existing list
void addAddress(const isc::asiolink::IOAddress& addr);
protected:
diff --git a/src/lib/dhcp/option4_client_fqdn.cc b/src/lib/dhcp/option4_client_fqdn.cc
index 1984e8b7e3..add097bf60 100644
--- a/src/lib/dhcp/option4_client_fqdn.cc
+++ b/src/lib/dhcp/option4_client_fqdn.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -52,9 +52,9 @@ public:
/// @brief Constructor, from wire data.
///
- /// @param first An iterator pointing to the begining of the option data
+ /// @param first An iterator pointing to the beginning of the option data
/// in the wire format.
- /// @param last An iterator poiting to the end of the option data in the
+ /// @param last An iterator pointing to the end of the option data in the
/// wire format.
Option4ClientFqdnImpl(OptionBufferConstIter first,
OptionBufferConstIter last);
@@ -92,9 +92,9 @@ public:
/// @brief Parse the Option provided in the wire format.
///
- /// @param first An iterator pointing to the begining of the option data
+ /// @param first An iterator pointing to the beginning of the option data
/// in the wire format.
- /// @param last An iterator poiting to the end of the option data in the
+ /// @param last An iterator pointing to the end of the option data in the
/// wire format.
void parseWireData(OptionBufferConstIter first,
OptionBufferConstIter last);
@@ -104,11 +104,11 @@ public:
void parseCanonicalDomainName(OptionBufferConstIter first,
OptionBufferConstIter last);
- /// @brief Parse domain-name emcoded in the deprecated ASCII format.
+ /// @brief Parse domain-name encoded in the deprecated ASCII format.
///
- /// @param first An iterator pointing to the begining of the option data
+ /// @param first An iterator pointing to the beginning of the option data
/// where domain-name is stored.
- /// @param last An iterator poiting to the end of the option data where
+ /// @param last An iterator pointing to the end of the option data where
/// domain-name is stored.
void parseASCIIDomainName(OptionBufferConstIter first,
OptionBufferConstIter last);
@@ -121,7 +121,7 @@ Option4ClientFqdnImpl(const uint8_t flags,
const std::string& domain_name,
// cppcheck 1.57 complains that const enum value is not passed
// by reference. Note that, it accepts the non-const enum value
- // to be passed by value. In both cases it is unneccessary to
+ // to be passed by value. In both cases it is unnecessary to
// pass the enum by reference.
// cppcheck-suppress passedByValue
const Option4ClientFqdn::DomainNameType name_type)
@@ -188,7 +188,7 @@ Option4ClientFqdnImpl::
setDomainName(const std::string& domain_name,
// cppcheck 1.57 complains that const enum value is not passed
// by reference. Note that, it accepts the non-const enum
- // to be passed by value. In both cases it is unneccessary to
+ // to be passed by value. In both cases it is unnecessary to
// pass the enum by reference.
// cppcheck-suppress passedByValue
const Option4ClientFqdn::DomainNameType name_type) {
diff --git a/src/lib/dhcp/option6_client_fqdn.cc b/src/lib/dhcp/option6_client_fqdn.cc
index e1c7c565c7..77c87ef0d9 100644
--- a/src/lib/dhcp/option6_client_fqdn.cc
+++ b/src/lib/dhcp/option6_client_fqdn.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -47,9 +47,9 @@ public:
/// @brief Constructor, from wire data.
///
- /// @param first An iterator pointing to the begining of the option data
+ /// @param first An iterator pointing to the beginning of the option data
/// in the wire format.
- /// @param last An iterator poiting to the end of the option data in the
+ /// @param last An iterator pointing to the end of the option data in the
/// wire format.
Option6ClientFqdnImpl(OptionBufferConstIter first,
OptionBufferConstIter last);
@@ -87,9 +87,9 @@ public:
/// @brief Parse the Option provided in the wire format.
///
- /// @param first An iterator pointing to the begining of the option data
+ /// @param first An iterator pointing to the beginning of the option data
/// in the wire format.
- /// @param last An iterator poiting to the end of the option data in the
+ /// @param last An iterator pointing to the end of the option data in the
/// wire format.
void parseWireData(OptionBufferConstIter first,
OptionBufferConstIter last);
diff --git a/src/lib/dhcp/option6_iaprefix.cc b/src/lib/dhcp/option6_iaprefix.cc
index 4bfa672f9b..f47f49d1a8 100644
--- a/src/lib/dhcp/option6_iaprefix.cc
+++ b/src/lib/dhcp/option6_iaprefix.cc
@@ -133,7 +133,7 @@ Option6IAPrefix::mask(OptionBuffer::const_iterator begin,
std::copy(begin, begin + static_cast<uint8_t>(len/8), output_address.begin());
// The remaining significant bits of the last octet have to be left unchanged,
// but the remaining bits of this octet must be set to zero. The number of
- // significant bits is calculated as a reminder from the devision of the
+ // significant bits is calculated as a reminder from the division of the
// prefix length by 8 (by size of the octet). The number of bits to be set
// to zero is therefore calculated as: 8 - (len % 8).
// Next, the mask is created by shifting the 0xFF by the number of bits
diff --git a/src/lib/dhcp/option6_iaprefix.h b/src/lib/dhcp/option6_iaprefix.h
index e3a9b57ef5..0020de00f4 100644
--- a/src/lib/dhcp/option6_iaprefix.h
+++ b/src/lib/dhcp/option6_iaprefix.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -44,7 +44,7 @@ namespace dhcp {
/// that the prefixes from the string are created locally (not received over the
/// wire) and should be validated before the option is created. If we wanted
/// to set non-significant bits to 0 when the prefix is created from the textual
-/// format it would have some peformance implications, because the option would
+/// format it would have some performance implications, because the option would
/// need to be turned into wire format, appropriate bits set to 0 and then
/// option would need to be created again from the wire format. We may consider
/// doing it if we find a use case where it is required.
diff --git a/src/lib/dhcp/option6_pdexclude.h b/src/lib/dhcp/option6_pdexclude.h
index 3b9f880bba..2b6e0755a9 100644
--- a/src/lib/dhcp/option6_pdexclude.h
+++ b/src/lib/dhcp/option6_pdexclude.h
@@ -27,7 +27,7 @@ public:
/// @brief Constructor.
///
- /// @param delegated_prefix Delagated prefix.
+ /// @param delegated_prefix Delegated prefix.
/// @param delegated_prefix_length Delegated prefix length.
/// @param excluded_prefix Excluded prefix.
/// @param excluded_prefix_length Excluded prefix length.
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
index 643459f11a..023d71424d 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -65,6 +65,26 @@ OptionCustom::addArrayDataField(const IOAddress& address) {
}
void
+OptionCustom::addArrayDataField(const std::string& value) {
+ checkArrayType();
+
+ OpaqueDataTuple::LengthFieldType lft = getUniverse() == Option::V4 ?
+ OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES;
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeTuple(value, lft, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const OpaqueDataTuple& value) {
+ checkArrayType();
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeTuple(value, buf);
+ buffers_.push_back(buf);
+}
+
+void
OptionCustom::addArrayDataField(const bool value) {
checkArrayType();
@@ -242,9 +262,9 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// remaining part of the buffer for it. Note that variable
// size data can be laid at the end of the option only and
// that the validate() function in OptionDefinition object
- // should have checked wheter it is a case for this option.
+ // should have checked whether it is a case for this option.
data_size = std::distance(data, data_buf.end());
- } else if (*field == OPT_IPV6_PREFIX_TYPE ) {
+ } else if (*field == OPT_IPV6_PREFIX_TYPE) {
// The size of the IPV6 prefix type is determined as
// one byte (which is the size of the prefix in bits)
// followed by the prefix bits (right-padded with
@@ -252,6 +272,18 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
if (std::distance(data, data_buf.end()) > 0) {
data_size = static_cast<size_t>(sizeof(uint8_t) + (*data + 7) / 8);
}
+ } else if (*field == OPT_TUPLE_TYPE) {
+ OpaqueDataTuple::LengthFieldType lft =
+ getUniverse() == Option::V4 ?
+ OpaqueDataTuple::LENGTH_1_BYTE :
+ OpaqueDataTuple::LENGTH_2_BYTES;
+ std::string value =
+ OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()),
+ lft);
+ data_size = value.size();
+ // The size of the buffer holding a tuple is always
+ // 1 or 2 byte larger than the size of the string
+ data_size += getUniverse() == Option::V4 ? 1 : 2;
} else {
// If we reached the end of buffer we assume that this option is
// truncated because there is no remaining data to initialize
@@ -314,6 +346,19 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// Data size comprises 1 byte holding a prefix length and the
// prefix length (in bytes) rounded to the nearest byte boundary.
data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8;
+ } else if (data_type == OPT_TUPLE_TYPE) {
+ OpaqueDataTuple::LengthFieldType lft =
+ getUniverse() == Option::V4 ?
+ OpaqueDataTuple::LENGTH_1_BYTE :
+ OpaqueDataTuple::LENGTH_2_BYTES;
+ std::string value =
+ OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()),
+ lft);
+ data_size = value.size();
+ // The size of the buffer holding a tuple is always
+ // 1 or 2 byte larger than the size of the string
+ data_size += getUniverse() == Option::V4 ? 1 : 2;
+
}
// We don't perform other checks for data types that can't be
// used together with array indicator such as strings, empty field
@@ -351,6 +396,19 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
data_size = static_cast<size_t>
(sizeof(uint8_t) + (data_buf[0] + 7) / 8);
}
+ } else if (data_type == OPT_TUPLE_TYPE) {
+ OpaqueDataTuple::LengthFieldType lft =
+ getUniverse() == Option::V4 ?
+ OpaqueDataTuple::LENGTH_1_BYTE :
+ OpaqueDataTuple::LENGTH_2_BYTES;
+ std::string value =
+ OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()),
+ lft);
+ data_size = value.size();
+ // The size of the buffer holding a tuple is always
+ // 1 or 2 byte larger than the size of the string
+ data_size += getUniverse() == Option::V4 ? 1 : 2;
+
} else {
data_size = std::distance(data, data_buf.end());
}
@@ -415,6 +473,9 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
case OPT_FQDN_TYPE:
text << "\"" << readFqdn(index) << "\"";
break;
+ case OPT_TUPLE_TYPE:
+ text << "\"" << readTuple(index) << "\"";
+ break;
case OPT_STRING_TYPE:
text << "\"" << readString(index) << "\"";
break;
@@ -499,6 +560,39 @@ OptionCustom::writeBinary(const OptionBuffer& buf,
buffers_[index] = buf;
}
+std::string
+OptionCustom::readTuple(const uint32_t index) const {
+ checkIndex(index);
+ OpaqueDataTuple::LengthFieldType lft = getUniverse() == Option::V4 ?
+ OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES;
+ return (OptionDataTypeUtil::readTuple(buffers_[index], lft));
+}
+
+void
+OptionCustom::readTuple(OpaqueDataTuple& tuple,
+ const uint32_t index) const {
+ checkIndex(index);
+ OptionDataTypeUtil::readTuple(buffers_[index], tuple);
+}
+
+void
+OptionCustom::writeTuple(const std::string& value, const uint32_t index) {
+ checkIndex(index);
+
+ buffers_[index].clear();
+ OpaqueDataTuple::LengthFieldType lft = getUniverse() == Option::V4 ?
+ OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES;
+ OptionDataTypeUtil::writeTuple(value, lft, buffers_[index]);
+}
+
+void
+OptionCustom::writeTuple(const OpaqueDataTuple& value, const uint32_t index) {
+ checkIndex(index);
+
+ buffers_[index].clear();
+ OptionDataTypeUtil::writeTuple(value, buffers_[index]);
+}
+
bool
OptionCustom::readBoolean(const uint32_t index) const {
checkIndex(index);
@@ -523,7 +617,7 @@ void
OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
checkIndex(index);
- // Create a temporay buffer where the FQDN will be written.
+ // Create a temporary buffer where the FQDN will be written.
OptionBuffer buf;
// Try to write to the temporary buffer rather than to the
// buffers_ member directly guarantees that we don't modify
@@ -531,7 +625,7 @@ OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
// is valid.
OptionDataTypeUtil::writeFqdn(fqdn, buf);
// If we got to this point it means that the FQDN is valid.
- // We can move the contents of the teporary buffer to the
+ // We can move the contents of the temporary buffer to the
// target buffer.
std::swap(buffers_[index], buf);
}
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
index 122da7c9b9..14ab22916d 100644
--- a/src/lib/dhcp/option_custom.h
+++ b/src/lib/dhcp/option_custom.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -113,6 +113,16 @@ public:
buffers_.push_back(buf);
}
+ /// @brief Create new buffer and store tuple value in it
+ ///
+ /// @param value value to be stored as a tuple in the created buffer.
+ void addArrayDataField(const std::string& value);
+
+ /// @brief Create new buffer and store tuple value in it
+ ///
+ /// @param value value to be stored as a tuple in the created buffer.
+ void addArrayDataField(const OpaqueDataTuple& value);
+
/// @brief Create new buffer and store variable length prefix in it.
///
/// @param prefix_len Prefix length.
@@ -163,6 +173,34 @@ public:
/// @param index buffer index.
void writeBinary(const OptionBuffer& buf, const uint32_t index = 0);
+ /// @brief Read a buffer as length and string tuple.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @return string read from a buffer.
+ std::string readTuple(const uint32_t index = 0) const;
+
+ /// @brief Read a buffer into a length and string tuple.
+ ///
+ /// @param tuple tuple to fill.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void readTuple(OpaqueDataTuple& tuple, const uint32_t index = 0) const;
+
+ /// @brief Write a length and string tuple into a buffer.
+ ///
+ /// @param value value to be written.
+ /// @param index buffer index.
+ void writeTuple(const std::string& value, const uint32_t index = 0);
+
+ /// @brief Write a length and string tuple into a buffer.
+ ///
+ /// @param value value to be written.
+ /// @param index buffer index.
+ void writeTuple(const OpaqueDataTuple& value, const uint32_t index = 0);
+
/// @brief Read a buffer as boolean value.
///
/// @param index buffer index.
diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc
index 55a0145c67..ff3a6ae19b 100644
--- a/src/lib/dhcp/option_data_types.cc
+++ b/src/lib/dhcp/option_data_types.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
#include <dns/name.h>
#include <util/encode/hex.h>
#include <algorithm>
+#include <limits>
using namespace isc::asiolink;
@@ -30,6 +31,7 @@ OptionDataTypeUtil::OptionDataTypeUtil() {
data_types_["ipv6-prefix"] = OPT_IPV6_PREFIX_TYPE;
data_types_["psid"] = OPT_PSID_TYPE;
data_types_["string"] = OPT_STRING_TYPE;
+ data_types_["tuple"] = OPT_TUPLE_TYPE;
data_types_["fqdn"] = OPT_FQDN_TYPE;
data_types_["record"] = OPT_RECORD_TYPE;
@@ -47,6 +49,7 @@ OptionDataTypeUtil::OptionDataTypeUtil() {
data_type_names_[OPT_IPV6_PREFIX_TYPE] = "ipv6-prefix";
data_type_names_[OPT_PSID_TYPE] = "psid";
data_type_names_[OPT_STRING_TYPE] = "string";
+ data_type_names_[OPT_TUPLE_TYPE] = "tuple";
data_type_names_[OPT_FQDN_TYPE] = "fqdn";
data_type_names_[OPT_RECORD_TYPE] = "record";
// The "unknown" data type is declared here so as
@@ -141,7 +144,7 @@ OptionDataTypeUtil::readAddress(const std::vector<uint8_t>& buf,
return (IOAddress::fromBytes(AF_INET6, &buf[0]));
} else {
isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
- "IP address. Invalid family: " << family);
+ << " IP address. Invalid family: " << family);
}
}
@@ -170,6 +173,118 @@ OptionDataTypeUtil::writeBinary(const std::string& hex_str,
buf.insert(buf.end(), binary.begin(), binary.end());
}
+std::string
+OptionDataTypeUtil::readTuple(const std::vector<uint8_t>& buf,
+ OpaqueDataTuple::LengthFieldType lengthfieldtype) {
+ if (lengthfieldtype == OpaqueDataTuple::LENGTH_1_BYTE) {
+ if (buf.size() < 1) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " tuple (length). Invalid buffer size: "
+ << buf.size());
+ }
+ uint8_t len = buf[0];
+ if (buf.size() < 1 + len) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " tuple (length " << static_cast<unsigned>(len)
+ << "). Invalid buffer size: " << buf.size());
+ }
+ std::string value;
+ value.resize(len);
+ std::memcpy(&value[0], &buf[1], len);
+ return (value);
+ } else if (lengthfieldtype == OpaqueDataTuple::LENGTH_2_BYTES) {
+ if (buf.size() < 2) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " tuple (length). Invalid buffer size: "
+ << buf.size());
+ }
+ uint16_t len = isc::util::readUint16(&buf[0], 2);
+ if (buf.size() < 2 + len) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " tuple (length " << len
+ << "). Invalid buffer size: " << buf.size());
+ }
+ std::string value;
+ value.resize(len);
+ std::memcpy(&value[0], &buf[2], len);
+ return (value);
+ } else {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " tuple. Invalid length type field: "
+ << static_cast<unsigned>(lengthfieldtype));
+ }
+}
+
+void
+OptionDataTypeUtil::readTuple(const std::vector<uint8_t>& buf,
+ OpaqueDataTuple& tuple) {
+ try {
+ tuple.unpack(buf.begin(), buf.end());
+ } catch (const OpaqueDataTupleError& ex) {
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+void
+OptionDataTypeUtil::writeTuple(const std::string& value,
+ OpaqueDataTuple::LengthFieldType lengthfieldtype,
+ std::vector<uint8_t>& buf) {
+ if (lengthfieldtype == OpaqueDataTuple::LENGTH_1_BYTE) {
+ if (value.size() > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(BadDataTypeCast, "invalid tuple value (size "
+ << value.size() << " larger than "
+ << std::numeric_limits<uint8_t>::max() << ")");
+ }
+ buf.push_back(static_cast<uint8_t>(value.size()));
+
+ } else if (lengthfieldtype == OpaqueDataTuple::LENGTH_2_BYTES) {
+ if (value.size() > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(BadDataTypeCast, "invalid tuple value (size "
+ << value.size() << " larger than "
+ << std::numeric_limits<uint16_t>::max() << ")");
+ }
+ buf.resize(buf.size() + 2);
+ isc::util::writeUint16(static_cast<uint16_t>(value.size()),
+ &buf[buf.size() - 2], 2);
+ } else {
+ isc_throw(BadDataTypeCast, "unable to write data to the buffer as"
+ << " tuple. Invalid length type field: "
+ << static_cast<unsigned>(lengthfieldtype));
+ }
+ buf.insert(buf.end(), value.begin(), value.end());
+}
+
+void
+OptionDataTypeUtil::writeTuple(const OpaqueDataTuple& tuple,
+ std::vector<uint8_t>& buf) {
+ if (tuple.getLength() == 0) {
+ isc_throw(BadDataTypeCast, "invalid empty tuple value");
+ }
+ if (tuple.getLengthFieldType() == OpaqueDataTuple::LENGTH_1_BYTE) {
+ if (tuple.getLength() > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(BadDataTypeCast, "invalid tuple value (size "
+ << tuple.getLength() << " larger than "
+ << std::numeric_limits<uint8_t>::max() << ")");
+ }
+ buf.push_back(static_cast<uint8_t>(tuple.getLength()));
+
+ } else if (tuple.getLengthFieldType() == OpaqueDataTuple::LENGTH_2_BYTES) {
+ if (tuple.getLength() > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(BadDataTypeCast, "invalid tuple value (size "
+ << tuple.getLength() << " larger than "
+ << std::numeric_limits<uint16_t>::max() << ")");
+ }
+ buf.resize(buf.size() + 2);
+ isc::util::writeUint16(static_cast<uint16_t>(tuple.getLength()),
+ &buf[buf.size() - 2], 2);
+ } else {
+ isc_throw(BadDataTypeCast, "unable to write data to the buffer as"
+ << " tuple. Invalid length type field: "
+ << tuple.getLengthFieldType());
+ }
+ buf.insert(buf.end(), tuple.getData().begin(), tuple.getData().end());
+}
+
bool
OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
if (buf.empty()) {
@@ -420,7 +535,12 @@ OptionDataTypeUtil::readPsid(const std::vector<uint8_t>& buf) {
// All is good, so we can convert the PSID value read from the buffer to
// the port set number.
- psid = psid >> (sizeof(psid) * 8 - psid_len);
+ if (psid_len == sizeof(psid) * 8) {
+ // Shift by 16 always gives zero (CID 1398333)
+ psid = 0;
+ } else {
+ psid = psid >> (sizeof(psid) * 8 - psid_len);
+ }
return (std::make_pair(PSIDLen(psid_len), PSID(psid)));
}
diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h
index 7532747374..ffc6669dcb 100644
--- a/src/lib/dhcp/option_data_types.h
+++ b/src/lib/dhcp/option_data_types.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#define OPTION_DATA_TYPES_H
#include <asiolink/io_address.h>
+#include <dhcp/opaque_data_tuple.h>
#include <dhcp/option.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
@@ -57,6 +58,7 @@ enum OptionDataType {
OPT_IPV6_PREFIX_TYPE,
OPT_PSID_TYPE,
OPT_STRING_TYPE,
+ OPT_TUPLE_TYPE,
OPT_FQDN_TYPE,
OPT_RECORD_TYPE,
OPT_UNKNOWN_TYPE
@@ -341,7 +343,7 @@ public:
/// @brief Get data type buffer length.
///
/// This function returns the size of a particular data type.
- /// Values retured by this function correspond to the data type
+ /// Values returned by this function correspond to the data type
/// sizes defined in OptionDataTypeTraits (IPV4_ADDRESS_TYPE and
/// IPV6_ADDRESS_TYPE are exceptions here) so they rather indicate
/// the fixed length of the data being written into the buffer,
@@ -382,6 +384,42 @@ public:
static void writeBinary(const std::string& hex_str,
std::vector<uint8_t>& buf);
+ /// @brief Read length and string tuple from a buffer.
+ ///
+ /// @param buf input buffer.
+ /// @param lengthfieldtype LENGTH_1_BYTE (DHCPv4) or LENGTH_2_BYTES (DHCPv6)
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated.
+ /// @return string being read.
+ static std::string readTuple(const std::vector<uint8_t>& buf,
+ OpaqueDataTuple::LengthFieldType lengthfieldtype);
+
+ /// @brief Read length and string tuple from a buffer.
+ ///
+ /// @param buf input buffer.
+ /// @param tuple reference of the tuple to read into
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated.
+ /// @return tuple being read.
+ static void readTuple(const std::vector<uint8_t>& buf,
+ OpaqueDataTuple& tuple);
+
+ /// @brief Append length and string tuple to a buffer
+ ///
+ /// @param value length and string tuple
+ /// @param lengthfieldtype LENGTH_1_BYTE (DHCPv4) or LENGTH_2_BYTES (DHCPv6)
+ /// @param [out] buf output buffer.
+ static void writeTuple(const std::string& value,
+ OpaqueDataTuple::LengthFieldType lengthfieldtype,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Append length and string tuple to a buffer
+ ///
+ /// @param tuple length and string tuple
+ /// @param [out] buf output buffer.
+ static void writeTuple(const OpaqueDataTuple& tuple,
+ std::vector<uint8_t>& buf);
+
/// @brief Read boolean value from a buffer.
///
/// @param buf input buffer.
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index a651a3d247..fac287e749 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -26,10 +26,13 @@
#include <dhcp/option_vendor.h>
#include <dhcp/option_vendor_class.h>
#include <util/encode/hex.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
#include <util/strutil.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/dynamic_bitset.hpp>
+#include <sstream>
using namespace std;
using namespace isc::util;
@@ -205,6 +208,14 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
case OPT_STRING_TYPE:
return (OptionPtr(new OptionString(u, type, begin, end)));
+ case OPT_TUPLE_TYPE:
+ // Handle array type only here (see comments for
+ // OPT_IPV4_ADDRESS_TYPE case).
+ if (array_type_) {
+ return (factoryOpaqueDataTuples(u, type, begin, end));
+ }
+ break;
+
default:
// Do nothing. We will return generic option a few lines down.
;
@@ -231,11 +242,11 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
isc_throw(InvalidOptionValue, "no option value specified");
}
} else {
- writeToBuffer(util::str::trim(values[0]), type_, buf);
+ writeToBuffer(u, util::str::trim(values[0]), type_, buf);
}
} else if (array_type_ && type_ != OPT_RECORD_TYPE) {
for (size_t i = 0; i < values.size(); ++i) {
- writeToBuffer(util::str::trim(values[i]), type_, buf);
+ writeToBuffer(u, util::str::trim(values[i]), type_, buf);
}
} else if (type_ == OPT_RECORD_TYPE) {
const RecordFieldsCollection& records = getRecordFields();
@@ -245,8 +256,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
<< " of values provided.");
}
for (size_t i = 0; i < records.size(); ++i) {
- writeToBuffer(util::str::trim(values[i]),
- records[i], buf);
+ writeToBuffer(u, util::str::trim(values[i]), records[i], buf);
}
}
return (optionFactory(u, type, buf.begin(), buf.end()));
@@ -433,12 +443,17 @@ OptionDefinition::haveStatusCodeFormat() const {
bool
OptionDefinition::haveOpaqueDataTuplesFormat() const {
- return (getType() == OPT_BINARY_TYPE);
+ return (haveType(OPT_TUPLE_TYPE) && getArrayType());
+}
+
+bool
+OptionDefinition::haveCompressedFqdnListFormat() const {
+ return (haveType(OPT_FQDN_TYPE) && getArrayType());
}
bool
OptionDefinition::convertToBool(const std::string& value_str) const {
- // Case insensitve check that the input is one of: "true" or "false".
+ // Case-insensitive check that the input is one of: "true" or "false".
if (boost::iequals(value_str, "true")) {
return (true);
@@ -489,8 +504,15 @@ OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str)
result = boost::lexical_cast<int64_t>(value_str);
} catch (const boost::bad_lexical_cast&) {
- isc_throw(BadDataTypeCast, "unable to convert the value '"
- << value_str << "' to integer data type");
+ // boost::lexical_cast does not handle hexadecimal
+ // but stringstream does so do it the hard way.
+ std::stringstream ss;
+ ss << std::hex << value_str;
+ ss >> result;
+ if (ss.fail() || !ss.eof()) {
+ isc_throw(BadDataTypeCast, "unable to convert the value '"
+ << value_str << "' to integer data type");
+ }
}
// Perform range checks.
if (OptionDataTypeTraits<T>::integer_type) {
@@ -507,7 +529,8 @@ OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str)
}
void
-OptionDefinition::writeToBuffer(const std::string& value,
+OptionDefinition::writeToBuffer(Option::Universe u,
+ const std::string& value,
const OptionDataType type,
OptionBuffer& buf) const {
// We are going to write value given by value argument to the buffer.
@@ -652,6 +675,13 @@ OptionDefinition::writeToBuffer(const std::string& value,
case OPT_FQDN_TYPE:
OptionDataTypeUtil::writeFqdn(value, buf);
return;
+ case OPT_TUPLE_TYPE:
+ {
+ OpaqueDataTuple::LengthFieldType lft = u == Option::V4 ?
+ OpaqueDataTuple::LENGTH_1_BYTE : OpaqueDataTuple::LENGTH_2_BYTES;
+ OptionDataTypeUtil::writeTuple(value, lft, buf);
+ return;
+ }
default:
// We hit this point because invalid option data type has been specified
// This may be the case because 'empty' or 'record' data type has been
@@ -739,6 +769,48 @@ OptionDefinition::factoryIAPrefix6(uint16_t type,
}
OptionPtr
+OptionDefinition::factoryOpaqueDataTuples(Option::Universe u,
+ uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ boost::shared_ptr<OptionOpaqueDataTuples>
+ option(new OptionOpaqueDataTuples(u, type, begin, end));
+
+ return (option);
+}
+
+OptionPtr
+OptionDefinition::factoryFqdnList(Option::Universe u,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const {
+
+ const std::vector<uint8_t> data(begin, end);
+ if (data.empty()) {
+ isc_throw(InvalidOptionValue, "FQDN list option has invalid length of 0");
+ }
+ InputBuffer in_buf(static_cast<const void*>(&data[0]), data.size());
+ std::vector<uint8_t> out_buf;
+ out_buf.reserve(data.size());
+ while (in_buf.getPosition() < in_buf.getLength()) {
+ // Reuse readFqdn and writeFqdn code but on the whole buffer
+ // so the DNS name code handles compression for us.
+ try {
+ isc::dns::Name name(in_buf);
+ isc::dns::LabelSequence labels(name);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* label = labels.getData(&read_len);
+ out_buf.insert(out_buf.end(), label, label + read_len);
+ }
+ } catch (const isc::Exception& ex) {
+ isc_throw(InvalidOptionValue, ex.what());
+ }
+ }
+ return OptionPtr(new OptionCustom(*this, u,
+ out_buf.begin(), out_buf.end()));
+}
+
+OptionPtr
OptionDefinition::factorySpecialFormatOption(Option::Universe u,
OptionBufferConstIter begin,
OptionBufferConstIter end) const {
@@ -756,7 +828,7 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u,
return (factoryIA6(getCode(), begin, end));
} else if (getCode() == D6O_IAADDR && haveIAAddr6Format()) {
- // Rerurn Option6IAAddr option instance for the IAADDR
+ // Return Option6IAAddr option instance for the IAADDR
// option only for the same reasons as described in
// for IA_NA and IA_PD above.
return (factoryIAAddr6(getCode(), begin, end));
@@ -777,7 +849,7 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u,
return (OptionPtr(new Option6StatusCode(begin, end)));
} else if (getCode() == D6O_BOOTFILE_PARAM && haveOpaqueDataTuplesFormat()) {
// Bootfile params (option code 60)
- return (OptionPtr(new OptionOpaqueDataTuples(Option::V6, getCode(), begin, end)));
+ return (factoryOpaqueDataTuples(Option::V6, getCode(), begin, end));
} else if ((getCode() == D6O_PD_EXCLUDE) && haveType(OPT_IPV6_PREFIX_TYPE)) {
// Prefix Exclude (option code 67)
return (OptionPtr(new Option6PDExclude(begin, end)));
@@ -785,6 +857,8 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u,
} else {
if ((getCode() == DHO_FQDN) && haveFqdn4Format()) {
return (OptionPtr(new Option4ClientFqdn(begin, end)));
+ } else if (haveCompressedFqdnListFormat()) {
+ return (factoryFqdnList(Option::V4, begin, end));
} else if ((getCode() == DHO_VIVCO_SUBOPTIONS) &&
haveVendorClass4Format()) {
// V-I Vendor Class (option code 124).
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index b90f5d5a79..e1e89060b2 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -85,7 +85,9 @@ class OptionIntArray;
/// value. For example, DHCPv6 option 8 comprises a two-byte option code, a
/// two-byte option length and two-byte field that carries a uint16 value
/// (RFC 3315 - http://ietf.org/rfc/rfc3315.txt). In such a case, the option
-/// type is defined as "uint16".
+/// type is defined as "uint16". Length and string tuples are a length
+/// on one (DHCPv4) or two (DHCPv6) bytes followed by a string of
+/// the given length.
///
/// When the option has a more complex structure, the option type may be
/// defined as "array", "record" or even "array of records".
@@ -102,7 +104,7 @@ class OptionIntArray;
/// option type is used. In such cases the data field types within the record
/// are specified using \ref OptionDefinition::addRecordField.
///
-/// When the OptionDefinition object has been sucessfully created, it can be
+/// When the OptionDefinition object has been successfully created, it can be
/// queried to return the appropriate option factory function for the specified
/// specified option format. There are a number of "standard" factory functions
/// that cover well known (common) formats. If the particular format does not
@@ -123,6 +125,7 @@ class OptionIntArray;
/// - "psid" (PSID length / value)
/// - "string"
/// - "fqdn" (fully qualified name)
+/// - "tuple" (length and string)
/// - "record" (set of data fields of different types)
///
/// @todo Extend the comment to describe "generic factories".
@@ -371,6 +374,9 @@ public:
/// @return true if option has the format of OpaqueDataTuples type options.
bool haveOpaqueDataTuplesFormat() const;
+ /// @brief Check if the option has format of CompressedFqdnList options.
+ bool haveCompressedFqdnListFormat() const;
+
/// @brief Option factory.
///
/// This function creates an instance of DHCP option using
@@ -518,6 +524,21 @@ public:
OptionBufferConstIter begin,
OptionBufferConstIter end);
+ /// @brief Factory to create option with tuple list.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer
+ /// with a list of tuples.
+ /// @param end iterator pointing to the end of the buffer with
+ /// a list of tuples.
+ ///
+ /// @return instance of the DHCP option.
+ static OptionPtr factoryOpaqueDataTuples(Option::Universe u,
+ uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
/// @brief Factory function to create option with integer value.
///
/// @param u universe (V4 or V6).
@@ -561,6 +582,19 @@ public:
private:
+ /// @brief Factory function to create option with a compressed FQDN list.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
+ ///
+ /// @return instance of the DHCP option where FQDNs are uncompressed.
+ /// @throw InvalidOptionValue if data for the option is invalid.
+ OptionPtr factoryFqdnList(Option::Universe u,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const;
+
/// @brief Creates an instance of an option having special format.
///
/// The option with special formats are encapsulated by the dedicated
@@ -624,7 +658,7 @@ private:
/// This function performs lexical cast of a string value to integer
/// value and checks if the resulting value is within a range of a
/// target type. The target type should be one of the supported
- /// integer types.
+ /// integer types. Hexadecimal input is supported.
///
/// @param value_str input value given as string.
/// @tparam T target integer type for lexical cast.
@@ -644,13 +678,14 @@ private:
/// if it is successful it will store the data in the buffer
/// in a binary format.
///
+ /// @param u option universe (V4 or V6).
/// @param value string representation of the value to be written.
/// @param type the actual data type to be stored.
/// @param [in, out] buf buffer where the value is to be stored.
///
/// @throw BadDataTypeCast if data write was unsuccessful.
- void writeToBuffer(const std::string& value, const OptionDataType type,
- OptionBuffer& buf) const;
+ void writeToBuffer(Option::Universe u, const std::string& value,
+ const OptionDataType type, OptionBuffer& buf) const;
/// Option name.
std::string name_;
@@ -658,7 +693,7 @@ private:
uint16_t code_;
/// Option data type.
OptionDataType type_;
- /// Indicates wheter option is a single value or array.
+ /// Indicates whether option is a single value or array.
bool array_type_;
/// Name of the space being encapsulated by this option.
std::string encapsulated_space_;
diff --git a/src/lib/dhcp/option_space_container.h b/src/lib/dhcp/option_space_container.h
index 6b557ab58b..8a7736f46d 100644
--- a/src/lib/dhcp/option_space_container.h
+++ b/src/lib/dhcp/option_space_container.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -71,7 +71,7 @@ public:
/// @return a list of option spaces.
///
/// @todo This function is likely to be removed once
- /// we create a structore of OptionSpaces defined
+ /// we create a structure of OptionSpaces defined
/// through the configuration manager.
std::list<Selector> getOptionSpaceNames() const {
std::list<Selector> names;
diff --git a/src/lib/dhcp/option_string.h b/src/lib/dhcp/option_string.h
index dfc907f77a..51be5c2394 100644
--- a/src/lib/dhcp/option_string.h
+++ b/src/lib/dhcp/option_string.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -89,7 +89,7 @@ public:
///
/// This function decodes option data from the provided buffer. Note that
/// it does not decode the option code and length, so the iterators must
- /// point to the begining and end of the option payload respectively.
+ /// point to the beginning and end of the option payload respectively.
/// The size of the decoded payload must be at least 1 byte.
///
/// @param begin the iterator pointing to the option payload.
diff --git a/src/lib/dhcp/option_vendor_class.cc b/src/lib/dhcp/option_vendor_class.cc
index e6ae79c754..1d5d8a8e4b 100644
--- a/src/lib/dhcp/option_vendor_class.cc
+++ b/src/lib/dhcp/option_vendor_class.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -167,7 +167,7 @@ OptionVendorClass::toText(int indent) const {
// Apply indentation
s << std::string(indent, ' ');
- // Print type, length and first occurence of enterprise id.
+ // Print type, length and first occurrence of enterprise id.
s << "type=" << getType() << ", len=" << len() - getHeaderLen() << ", "
" enterprise id=0x" << std::hex << getVendorId() << std::dec;
// Iterate over all tuples and print their size and contents.
diff --git a/src/lib/dhcp/option_vendor_class.h b/src/lib/dhcp/option_vendor_class.h
index 14a29a3030..e4a8664837 100644
--- a/src/lib/dhcp/option_vendor_class.h
+++ b/src/lib/dhcp/option_vendor_class.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -24,7 +24,7 @@ namespace dhcp {
/// The format of DHCPv6 Vendor Class option (16) is described in section 22.16
/// of RFC3315 and the format of the DHCPv4 V-I Vendor Class option (124) is
/// described in section 3 of RFC3925. Each of these options carries enterprise
-/// id followed by the collection of tuples carring opaque data. A single tuple
+/// id followed by the collection of tuples carrying opaque data. A single tuple
/// consists of the field holding opaque data length and the actual data.
/// In case of the DHCPv4 V-I Vendor Class each tuple is preceded by the
/// 4-byte long enterprise id. Also, the field which carries the length of
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index 2b66490d70..efac8825c0 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -383,7 +383,7 @@ std::string
Pkt4::toText() const {
stringstream output;
output << "local_address=" << local_addr_ << ":" << local_port_
- << ", remote_adress=" << remote_addr_
+ << ", remote_address=" << remote_addr_
<< ":" << remote_port_ << ", msg_type=";
// Try to obtain message type.
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index d98533a591..bd3af7d1fe 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -73,7 +73,7 @@ public:
/// Prepares on-wire format of message and all its options.
/// Options must be stored in options_ field.
/// Output buffer will be stored in buffer_out_.
- /// The buffer_out_ is cleared before writting to the buffer.
+ /// The buffer_out_ is cleared before writing to the buffer.
///
/// @throw InvalidOperation if packing fails
virtual void pack();
@@ -113,7 +113,7 @@ public:
const ClientIdPtr& client_id,
const uint32_t transid);
- /// @brief Returns text representation of the given packet identifers.
+ /// @brief Returns text representation of the given packet identifiers.
///
/// This variant of the method does not include transaction id.
///
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index bb6c9723ec..19de2f7a76 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -37,6 +37,19 @@ Pkt6::RelayInfo::RelayInfo()
peeraddr_(DEFAULT_ADDRESS6), relay_msg_len_(0) {
}
+std::string Pkt6::RelayInfo::toText() const {
+ stringstream tmp;
+ tmp << "msg-type=" << static_cast<int>(msg_type_) << "(" << getName(msg_type_)
+ << "), hop-count=" << static_cast<int>(hop_count_) << "," << endl
+ << "link-address=" << linkaddr_.toText()
+ << ", peer-address=" << peeraddr_.toText() << ", "
+ << options_.size() << " option(s)" << endl;
+ for (auto option = options_.cbegin(); option != options_.cend(); ++option) {
+ tmp << option->second->toText() << endl;
+ }
+ return (tmp.str());
+}
+
Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */)
:Pkt(buf, buf_len, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0),
proto_(proto), msg_type_(0) {
@@ -268,7 +281,7 @@ Pkt6::pack() {
void
Pkt6::packUDP() {
try {
- // Make sure that the buffer is empty before we start writting to it.
+ // Make sure that the buffer is empty before we start writing to it.
buffer_out_.clear();
// is this a relayed packet?
@@ -314,7 +327,7 @@ Pkt6::packUDP() {
}
- // DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
+ // DHCPv6 header: message-type (1 octet) + transaction id (3 octets)
buffer_out_.writeUint8(msg_type_);
// store 3-octet transaction-id
buffer_out_.writeUint8( (transid_ >> 16) & 0xff );
@@ -369,7 +382,7 @@ Pkt6::unpackUDP() {
case DHCPV6_INFORMATION_REQUEST:
case DHCPV6_DHCPV4_QUERY:
case DHCPV6_DHCPV4_RESPONSE:
- default: // assume that uknown messages are not using relay format
+ default: // assume that unknown messages are not using relay format
{
return (unpackMsg(data_.begin(), data_.end()));
}
@@ -429,7 +442,7 @@ Pkt6::unpackRelayMsg() {
// we use offset + bufsize, because we want to avoid creating unnecessary
// copies. There may be up to 32 relays. While using InputBuffer would
// be probably a bit cleaner, copying data up to 32 times is unacceptable
- // price here. Hence a single buffer with offets and lengths.
+ // price here. Hence a single buffer with offsets and lengths.
size_t bufsize = data_.size();
size_t offset = 0;
@@ -586,7 +599,7 @@ Pkt6::makeLabel(const DuidPtr duid, const HWAddrPtr& hwaddr) {
label << "duid=[" << (duid ? duid->toText() : "no info")
<< "]";
- // HW address is typically not carried in the DHCPv6 mmessages
+ // HW address is typically not carried in the DHCPv6 messages
// and can be extracted using various, but not fully reliable,
// techniques. If it is not present, don't print anything.
if (hwaddr) {
@@ -607,16 +620,31 @@ Pkt6::getLabel() const {
std::string
Pkt6::toText() const {
stringstream tmp;
+
+ // First print the basics
tmp << "localAddr=[" << local_addr_ << "]:" << local_port_
- << " remoteAddr=[" << remote_addr_
- << "]:" << remote_port_ << endl;
- tmp << "msgtype=" << static_cast<int>(msg_type_) << ", transid=0x" <<
+ << " remoteAddr=[" << remote_addr_ << "]:" << remote_port_ << endl;
+ tmp << "msgtype=" << static_cast<int>(msg_type_) << "(" << getName(msg_type_)
+ << "), transid=0x" <<
hex << transid_ << dec << endl;
+
+ // Then print the options
for (isc::dhcp::OptionCollection::const_iterator opt=options_.begin();
opt != options_.end();
++opt) {
tmp << opt->second->toText() << std::endl;
}
+
+ // Finally, print the relay information (if present)
+ if (!relay_info_.empty()) {
+ tmp << relay_info_.size() << " relay(s):" << endl;
+ int cnt = 0;
+ for (auto relay = relay_info_.cbegin(); relay != relay_info_.cend(); ++relay) {
+ tmp << "relay[" << cnt++ << "]: " << relay->toText();
+ }
+ } else {
+ tmp << "No relays traversed." << endl;
+ }
return tmp.str();
}
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index 6abe3db6df..e7bac67de7 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -86,6 +86,11 @@ public:
/// @brief default constructor
RelayInfo();
+
+ /// @brief Returns printable representation of the relay information.
+ /// @return text representation of the structure (used in debug logging)
+ std::string toText() const;
+
uint8_t msg_type_; ///< message type (RELAY-FORW oro RELAY-REPL)
uint8_t hop_count_; ///< number of traversed relays (up to 32)
isc::asiolink::IOAddress linkaddr_;///< fixed field in relay-forw/relay-reply
diff --git a/src/lib/dhcp/pkt_filter.h b/src/lib/dhcp/pkt_filter.h
index e310500cd1..90a2259730 100644
--- a/src/lib/dhcp/pkt_filter.h
+++ b/src/lib/dhcp/pkt_filter.h
@@ -109,7 +109,7 @@ protected:
///
/// This method provides a means to open a fallback socket and bind it
/// to a given IPv4 address and UDP port. This function may be used by the
- /// derived classes to create a fallback socket. It can be overriden
+ /// derived classes to create a fallback socket. It can be overridden
/// in the derived classes if it happens to be insufficient on some
/// environments.
///
diff --git a/src/lib/dhcp/pkt_filter_bpf.cc b/src/lib/dhcp/pkt_filter_bpf.cc
index 474b8684e1..20d0098c68 100644
--- a/src/lib/dhcp/pkt_filter_bpf.cc
+++ b/src/lib/dhcp/pkt_filter_bpf.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -26,7 +26,7 @@ const unsigned int MAX_BPF_OPEN_ATTEMPTS = 100;
/// received on local loopback interface.
const unsigned int BPF_LOCAL_LOOPBACK_HEADER_LEN = 4;
-/// The following structure defines a Berkely Packet Filter program to perform
+/// The following structure defines a Berkeley Packet Filter program to perform
/// packet filtering. The program operates on Ethernet packets. To help with
/// interpretation of the program, for the types of Ethernet packets we are
/// interested in, the header layout is:
@@ -137,7 +137,7 @@ struct bpf_insn ethernet_ip_udp_filter [] = {
struct bpf_insn loopback_ip_udp_filter [] = {
// Make sure this is an IP packet. The pseudo header comprises a 4-byte
// long value identifying the address family, which should be set to
- // AF_INET. The default value used here (0xFFFFFFFF) must be overriden
+ // AF_INET. The default value used here (0xFFFFFFFF) must be overridden
// with htonl(AF_INET) from within the openSocket function.
// #0
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 0),
@@ -229,7 +229,7 @@ PktFilterBPF::openSocket(Iface& iface,
// Open fallback socket first. If it fails, it will give us an indication
// that there is another service (perhaps DHCP server) running.
- // The function will throw an exception and effectivelly cease opening
+ // The function will throw an exception and effectively cease opening
// the BPF device below.
int fallback = openFallbackSocket(addr, port);
@@ -310,7 +310,7 @@ PktFilterBPF::openSocket(Iface& iface,
close(fallback);
close(sock);
isc_throw(SocketConfigError, "Unable to obtain the required"
- " buffer legth for reads from BPF device");
+ " buffer length for reads from BPF device");
}
if (buf_len < sizeof(bpf_hdr)) {
@@ -355,14 +355,14 @@ PktFilterBPF::openSocket(Iface& iface,
}
// Configure the BPF device to use the immediate mode. This ensures
- // that the read function returns immediatelly, instead of waiting
+ // that the read function returns immediately, instead of waiting
// for the kernel to fill up the buffer, which would likely cause
// read hangs.
int flag = 1;
if (ioctl(sock, BIOCIMMEDIATE, &flag) < 0) {
close(fallback);
close(sock);
- isc_throw(SocketConfigError, "Failed to set promiscious mode for"
+ isc_throw(SocketConfigError, "Failed to set promiscuous mode for"
" BPF device");
}
@@ -411,10 +411,10 @@ PktFilterBPF::receive(Iface& iface, const SocketInfo& socket_info) {
datalen = read(socket_info.sockfd_, iface.getReadBuffer(),
iface.getReadBufferSize());
// If negative value is returned by read(), it indicates that an
- // error occured. If returned value is 0, no data was read from the
+ // error occurred. If returned value is 0, no data was read from the
// socket. In both cases something has gone wrong, because we expect
// that a chunk of data is there. We signal the lack of data by
- // returing an empty packet.
+ // returning an empty packet.
if (datalen <= 0) {
return Pkt4Ptr();
}
diff --git a/src/lib/dhcp/pkt_filter_bpf.h b/src/lib/dhcp/pkt_filter_bpf.h
index 62948ac109..9413848d9c 100644
--- a/src/lib/dhcp/pkt_filter_bpf.h
+++ b/src/lib/dhcp/pkt_filter_bpf.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -46,7 +46,7 @@ namespace dhcp {
/// In nutshell, the BPF device is created by opening the file /dev/bpf%d
/// where %d is a number. The BPF device is configured by issuing ioctl
/// commands listed here: http://www.freebsd.org/cgi/man.cgi?bpf(4).
-/// The specific configuration used by Kea DHCP server is decribed in
+/// The specific configuration used by Kea DHCP server is described in
/// documentation of @c PktFilterBPF::openSocket.
///
/// Use of BPF requires Kea to encode and decode the datalink and network
@@ -73,7 +73,7 @@ public:
/// - set filter program to receive DHCP messages encapsulated in UDP
/// packets
/// - set immediate mode which causes the read function to return
- /// immediatelly and do not wait for the whole read buffer to be filled
+ /// immediately and do not wait for the whole read buffer to be filled
/// by the kernel (to avoid hangs)
///
/// It also obtains the following configuration from the kernel:
diff --git a/src/lib/dhcp/pkt_filter_inet.h b/src/lib/dhcp/pkt_filter_inet.h
index 9075874da8..aef7a8f62c 100644
--- a/src/lib/dhcp/pkt_filter_inet.h
+++ b/src/lib/dhcp/pkt_filter_inet.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,7 +15,7 @@ namespace dhcp {
/// @brief Packet handling class using AF_INET socket family
///
-/// This class provides methods to send and recive packet via socket using
+/// This class provides methods to send and receive packet via socket using
/// AF_INET family and SOCK_DGRAM type.
class PktFilterInet : public PktFilter {
public:
@@ -28,7 +28,7 @@ public:
/// @brief Check if packet can be sent to the host without address directly.
///
/// This Packet Filter sends packets through AF_INET datagram sockets, so
- /// it can't inject the HW address of the destionation host into the packet.
+ /// it can't inject the HW address of the destination host into the packet.
/// Therefore this class does not support direct responses.
///
/// @return false always.
@@ -61,7 +61,7 @@ public:
/// @return Received packet
/// @throw isc::dhcp::SocketReadError if an error occurs during reception
/// of the packet.
- /// @throw An execption thrown by the isc::dhcp::Pkt4 object if DHCPv4
+ /// @throw An exception thrown by the isc::dhcp::Pkt4 object if DHCPv4
/// message parsing fails.
virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& socket_info);
@@ -72,7 +72,7 @@ public:
/// @param pkt packet to be sent
///
/// @return result of sending a packet. It is 0 if successful.
- /// @throw isc::dhcp::SocketWriteError if an error occures during sending
+ /// @throw isc::dhcp::SocketWriteError if an error occurs during sending
/// a DHCP message through the socket.
virtual int send(const Iface& iface, uint16_t sockfd,
const Pkt4Ptr& pkt);
diff --git a/src/lib/dhcp/pkt_filter_inet6.cc b/src/lib/dhcp/pkt_filter_inet6.cc
index 9fae4d9cd2..baa6ec7a9e 100644
--- a/src/lib/dhcp/pkt_filter_inet6.cc
+++ b/src/lib/dhcp/pkt_filter_inet6.cc
@@ -80,8 +80,12 @@ PktFilterInet6::openSocket(const Iface& iface,
// in6addr_any (binding to port). Binding to port is required on some
// operating systems, e.g. NetBSD and OpenBSD so as the socket can
// join the socket to multicast group.
- if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
- (char *)&flag, sizeof(flag)) < 0) {
+ // RedHat 6.4 defines SO_REUSEPORT but the kernel does not support it
+ // and returns ENOPROTOOPT so ignore this error. Other versions may be
+ // affected, too.
+ if ((setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
+ (char *)&flag, sizeof(flag)) < 0) &&
+ (errno != ENOPROTOOPT)) {
close(sock);
isc_throw(SocketConfigError, "Can't set SO_REUSEPORT option on IPv6"
" socket.");
diff --git a/src/lib/dhcp/pkt_filter_inet6.h b/src/lib/dhcp/pkt_filter_inet6.h
index 3c9dc79630..927a077872 100644
--- a/src/lib/dhcp/pkt_filter_inet6.h
+++ b/src/lib/dhcp/pkt_filter_inet6.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -41,7 +41,7 @@ public:
/// group.
///
/// @return A structure describing a primary and fallback socket.
- /// @throw isc::dhcp::SocketConfigError if error occured when opening
+ /// @throw isc::dhcp::SocketConfigError if error occurred when opening
/// or configuring a socket.
virtual SocketInfo openSocket(const Iface& iface,
const isc::asiolink::IOAddress& addr,
@@ -69,7 +69,7 @@ public:
/// @brief Sends DHCPv6 message through a specified interface and socket.
///
- /// Thie function sends a DHCPv6 message through a specified interface and
+ /// The function sends a DHCPv6 message through a specified interface and
/// socket. In general, there may be multiple sockets open on a single
/// interface as a single interface may have multiple IPv6 addresses.
///
@@ -78,7 +78,7 @@ public:
/// @param pkt A packet to be sent.
///
/// @return A result of sending the message. It is 0 if successful.
- /// @throw isc::dhcp::SocketWriteError if error occured when sending a
+ /// @throw isc::dhcp::SocketWriteError if error occurred when sending a
/// packet.
virtual int send(const Iface& iface, uint16_t sockfd,
const Pkt6Ptr& pkt);
diff --git a/src/lib/dhcp/pkt_filter_lpf.cc b/src/lib/dhcp/pkt_filter_lpf.cc
index 73ec38f81d..ba64b00776 100644
--- a/src/lib/dhcp/pkt_filter_lpf.cc
+++ b/src/lib/dhcp/pkt_filter_lpf.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12,16 +12,16 @@
#include <dhcp/protocol_util.h>
#include <exceptions/exceptions.h>
#include <fcntl.h>
+#include <net/ethernet.h>
#include <linux/filter.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
-#include <net/ethernet.h>
namespace {
using namespace isc::dhcp;
-/// The following structure defines a Berkely Packet Filter program to perform
+/// The following structure defines a Berkeley Packet Filter program to perform
/// packet filtering. The program operates on Ethernet packets. To help with
/// interpretation of the program, for the types of Ethernet packets we are
/// interested in, the header layout is:
@@ -136,7 +136,7 @@ PktFilterLPF::openSocket(Iface& iface,
// Open fallback socket first. If it fails, it will give us an indication
// that there is another service (perhaps DHCP server) running.
- // The function will throw an exception and effectivelly cease opening
+ // The function will throw an exception and effectively cease opening
// raw socket below.
int fallback = openFallbackSocket(addr, port);
@@ -227,10 +227,10 @@ PktFilterLPF::receive(Iface& iface, const SocketInfo& socket_info) {
// have to get the data from the raw socket too.
int data_len = read(socket_info.sockfd_, raw_buf, sizeof(raw_buf));
// If negative value is returned by read(), it indicates that an
- // error occured. If returned value is 0, no data was read from the
+ // error occurred. If returned value is 0, no data was read from the
// socket. In both cases something has gone wrong, because we expect
// that a chunk of data is there. We signal the lack of data by
- // returing an empty packet.
+ // returning an empty packet.
if (data_len <= 0) {
return Pkt4Ptr();
}
diff --git a/src/lib/dhcp/pkt_filter_lpf.h b/src/lib/dhcp/pkt_filter_lpf.h
index f0c9c863e0..04c2181f0a 100644
--- a/src/lib/dhcp/pkt_filter_lpf.h
+++ b/src/lib/dhcp/pkt_filter_lpf.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -16,7 +16,7 @@ namespace dhcp {
/// @brief Packet handling class using Linux Packet Filtering
///
-/// This class provides methods to send and recive DHCPv4 messages using raw
+/// This class provides methods to send and receive DHCPv4 messages using raw
/// sockets and Linux Packet Filtering. It is used by @c isc::dhcp::IfaceMgr
/// to send DHCPv4 messages to the hosts which don't have an IPv4 address
/// assigned yet.
diff --git a/src/lib/dhcp/protocol_util.h b/src/lib/dhcp/protocol_util.h
index c6fa7421fe..65fa2aef5a 100644
--- a/src/lib/dhcp/protocol_util.h
+++ b/src/lib/dhcp/protocol_util.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,7 +15,7 @@
namespace isc {
namespace dhcp {
-/// @brief Exception thrown when error occured during parsing packet's headers.
+/// @brief Exception thrown when error occurred during parsing packet's headers.
///
/// This exception is thrown when parsing link, Internet or Transport layer
/// header has failed.
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
index cc0e2fc43a..02815b4c5f 100644
--- a/src/lib/dhcp/std_option_defs.h
+++ b/src/lib/dhcp/std_option_defs.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -57,7 +57,7 @@ RECORD_DECL(VIVCO_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
//
// Three 1 byte fields to describe a network interface: type, major and minor
RECORD_DECL(CLIENT_NDI_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE);
-// A client identifer: a 1 byte type field followed by opaque data depending on the type
+// A client identifier: a 1 byte type field followed by opaque data depending on the type
RECORD_DECL(UUID_GUID_RECORDS, OPT_UINT8_TYPE, OPT_BINARY_TYPE);
/// @brief Definitions of standard DHCPv4 options.
@@ -177,7 +177,7 @@ const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
{ "fqdn", DHO_FQDN, OPT_RECORD_TYPE, false, RECORD_DEF(FQDN_RECORDS), "" },
{ "dhcp-agent-options", DHO_DHCP_AGENT_OPTIONS,
OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "dhcp-agent-options-space" },
- // Unfortunatelly the AUTHENTICATE option contains a 64-bit
+ // Unfortunately the AUTHENTICATE option contains a 64-bit
// data field called 'replay-detection' that can't be added
// as a record field to a custom option. Also, there is no
// dedicated option class to handle it so we simply return
@@ -192,12 +192,7 @@ const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
{ "uuid-guid", DHO_UUID_GUID, OPT_RECORD_TYPE, false, RECORD_DEF(UUID_GUID_RECORDS), "" },
{ "subnet-selection", DHO_SUBNET_SELECTION,
OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
- // The following options need a special encoding of data
- // being carried by them. Therefore, there is no way they can
- // be handled by OptionCustom. We may need to implement
- // dedicated classes to handle them. Until that happens
- // let's treat them as 'binary' options.
- { "domain-search", DHO_DOMAIN_SEARCH, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "domain-search", DHO_DOMAIN_SEARCH, OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
{ "vivco-suboptions", DHO_VIVCO_SUBOPTIONS, OPT_RECORD_TYPE,
false, RECORD_DEF(VIVCO_RECORDS), "" },
// Vendor-Identifying Vendor Specific Information option payload begins with a
@@ -215,7 +210,7 @@ const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
{ "vivso-suboptions", DHO_VIVSO_SUBOPTIONS, OPT_UINT32_TYPE,
false, NO_RECORD_DEF, "" }
- // @todo add definitions for all remaning options.
+ // @todo add definitions for all remaining options.
};
/// Number of option definitions defined.
@@ -269,9 +264,9 @@ RECORD_DECL(CLIENT_NII_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE);
///
/// @warning in this array, the initializers are provided for all
/// OptionDefParams struct's members despite initializers for
-/// 'records' and 'record_size' could be ommited for entries for
+/// 'records' and 'record_size' could be omitted for entries for
/// which 'type' does not equal to OPT_RECORD_TYPE. If initializers
-/// are ommitted the corresponding values should default to 0.
+/// are omitted the corresponding values should default to 0.
/// This however does not work on Solaris (GCC) which issues a
/// warning about lack of initializers for some struct members
/// causing build to fail.
@@ -285,7 +280,7 @@ const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = {
{ "preference", D6O_PREFERENCE, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
{ "elapsed-time", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
{ "relay-msg", D6O_RELAY_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
- // Unfortunatelly the AUTH option contains a 64-bit data field
+ // Unfortunately the AUTH option contains a 64-bit data field
// called 'replay-detection' that can't be added as a record
// field to a custom option. Also, there is no dedicated
// option class to handle it so we simply return binary
@@ -355,7 +350,7 @@ const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = {
{ "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
NO_RECORD_DEF, "" },
{ "bootfile-url", D6O_BOOTFILE_URL, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
- { "bootfile-param", D6O_BOOTFILE_PARAM, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "bootfile-param", D6O_BOOTFILE_PARAM, OPT_TUPLE_TYPE, true, NO_RECORD_DEF, "" },
{ "client-arch-type", D6O_CLIENT_ARCH_TYPE, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
{ "nii", D6O_NII, OPT_RECORD_TYPE, false, RECORD_DEF(CLIENT_NII_RECORDS), "" },
{ "erp-local-domain-name", D6O_ERP_LOCAL_DOMAIN_NAME, OPT_FQDN_TYPE, false,
@@ -376,12 +371,6 @@ const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = {
{ "timestamp", D6O_TIMESTAMP, OPT_BINARY_TYPE, false,
NO_RECORD_DEF, "" },
{ "aftr-name", D6O_AFTR_NAME, OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" },
- { "s46-cont-mape", D6O_S46_CONT_MAPE, OPT_EMPTY_TYPE, false, NO_RECORD_DEF,
- MAPE_V6_OPTION_SPACE },
- { "s46-cont-mapt", D6O_S46_CONT_MAPT, OPT_EMPTY_TYPE, false, NO_RECORD_DEF,
- MAPT_V6_OPTION_SPACE },
- { "s46-cont-lw", D6O_S46_CONT_LW, OPT_EMPTY_TYPE, false, NO_RECORD_DEF,
- LW_V6_OPTION_SPACE }
// @todo There is still a bunch of options for which we have to provide
// definitions but we don't do it because they are not really
@@ -393,14 +382,6 @@ const int STANDARD_V6_OPTION_DEFINITIONS_SIZE =
sizeof(STANDARD_V6_OPTION_DEFINITIONS) /
sizeof(STANDARD_V6_OPTION_DEFINITIONS[0]);
-// Option definitions that belong to two or more option spaces are defined here.
-const OptionDefParams OPTION_DEF_PARAMS_S46_BR = { "s46-br", D6O_S46_BR,
- OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" };
-const OptionDefParams OPTION_DEF_PARAMS_S46_RULE = { "s46-rule", D6O_S46_RULE,
- OPT_RECORD_TYPE, false, RECORD_DEF(S46_RULE), V4V6_RULE_OPTION_SPACE };
-const OptionDefParams OPTION_DEF_PARAMS_S46_PORTPARAMS = { "s46-portparams",
- D6O_S46_PORTPARAMS, OPT_RECORD_TYPE, false, RECORD_DEF(S46_PORTPARAMS), "" };
-
/// @brief Definitions of vendor-specific DHCPv6 options, defined by ISC.
/// 4o6-* options are used for inter-process communication. For details, see
/// http://kea.isc.org/wiki/Dhcp4o6Design
@@ -420,52 +401,43 @@ const int ISC_V6_OPTION_DEFINITIONS_SIZE =
/// @brief MAPE option definitions
const OptionDefParams MAPE_V6_OPTION_DEFINITIONS[] = {
- OPTION_DEF_PARAMS_S46_BR,
- OPTION_DEF_PARAMS_S46_RULE
};
const int MAPE_V6_OPTION_DEFINITIONS_SIZE =
sizeof(MAPE_V6_OPTION_DEFINITIONS) /
- sizeof(MAPE_V6_OPTION_DEFINITIONS[0]);
+ sizeof(const OptionDefParams);
/// @brief MAPT option definitions
const OptionDefParams MAPT_V6_OPTION_DEFINITIONS[] = {
- OPTION_DEF_PARAMS_S46_RULE,
- { "s46-dmr", D6O_S46_DMR, OPT_IPV6_PREFIX_TYPE, false, NO_RECORD_DEF, "" }
};
const int MAPT_V6_OPTION_DEFINITIONS_SIZE =
sizeof(MAPT_V6_OPTION_DEFINITIONS) /
- sizeof(MAPT_V6_OPTION_DEFINITIONS[0]);
+ sizeof(const OptionDefParams);
/// @brief LW option definitions
const OptionDefParams LW_V6_OPTION_DEFINITIONS[] = {
- OPTION_DEF_PARAMS_S46_BR,
- { "s46-v4v6bind", D6O_S46_V4V6BIND, OPT_RECORD_TYPE, false,
- RECORD_DEF(S46_V4V6BIND), V4V6_BIND_OPTION_SPACE }
};
const int LW_V6_OPTION_DEFINITIONS_SIZE =
sizeof(LW_V6_OPTION_DEFINITIONS) /
- sizeof(LW_V6_OPTION_DEFINITIONS[0]);
+ sizeof(const OptionDefParams);
/// @brief Rule option definitions
const OptionDefParams V4V6_RULE_OPTION_DEFINITIONS[] = {
- OPTION_DEF_PARAMS_S46_PORTPARAMS
};
const int V4V6_RULE_OPTION_DEFINITIONS_SIZE =
sizeof(V4V6_RULE_OPTION_DEFINITIONS) /
- sizeof(V4V6_RULE_OPTION_DEFINITIONS[0]);
+ sizeof(const OptionDefParams);
/// @brief Bind option definitions
const OptionDefParams V4V6_BIND_OPTION_DEFINITIONS[] = {
- OPTION_DEF_PARAMS_S46_PORTPARAMS
};
const int V4V6_BIND_OPTION_DEFINITIONS_SIZE =
sizeof(V4V6_BIND_OPTION_DEFINITIONS) /
- sizeof(V4V6_BIND_OPTION_DEFINITIONS[0]);
+ sizeof(const OptionDefParams);
} // unnamed namespace
diff --git a/src/lib/dhcp/tests/duid_factory_unittest.cc b/src/lib/dhcp/tests/duid_factory_unittest.cc
index 87fcf92501..1669983d5d 100644
--- a/src/lib/dhcp/tests/duid_factory_unittest.cc
+++ b/src/lib/dhcp/tests/duid_factory_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -180,7 +180,7 @@ DUIDFactoryTest::removeDefaultFile() const {
std::string
DUIDFactoryTest::readDefaultFile() const {
- return (dhcp::test::readFile(absolutePath(DEFAULT_DUID_FILE)));
+ return (isc::test::readFile(absolutePath(DEFAULT_DUID_FILE)));
}
std::vector<uint8_t>
@@ -347,7 +347,7 @@ TEST_F(DUIDFactoryTest, createLLTExplicitHtype) {
}
// This test verifies that the factory class creates DUID-LLT from
-// explcitly specified link layer address, when other parameters
+// explicitly specified link layer address, when other parameters
// are generated.
TEST_F(DUIDFactoryTest, createLLTExplicitLinkLayerAddress) {
ASSERT_NO_THROW(factory().createLLT(0, 0, toVector("121212121212")));
@@ -356,7 +356,7 @@ TEST_F(DUIDFactoryTest, createLLTExplicitLinkLayerAddress) {
// This test verifies that the factory function creates DUID-LLT from
// all values explicitly specified.
-TEST_F(DUIDFactoryTest, createLLTAllExplcitParameters) {
+TEST_F(DUIDFactoryTest, createLLTAllExplicitParameters) {
ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA,
toVector("24242424242424242424")));
testLLT("0008", "FAFAFAFA", true, "24242424242424242424");
diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.h b/src/lib/dhcp/tests/iface_mgr_test_config.h
index 9963089dbd..ab435d59bc 100644
--- a/src/lib/dhcp/tests/iface_mgr_test_config.h
+++ b/src/lib/dhcp/tests/iface_mgr_test_config.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -194,7 +194,7 @@ public:
///
/// The test uses stub implementation of packet filter object. It is
/// possible to configure that object to report having a capability
- /// to directly repond to clients which don't have an address yet.
+ /// to directly respond to clients which don't have an address yet.
/// This function sets this property for packet filter object.
///
/// @param direct_resp Value to be set.
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 5136a10ed3..7a49b403cd 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -119,7 +119,7 @@ TEST(IfaceTest, countActive4) {
/// TestPktFilter::receive will never be called. The appropriate extension
/// to IfaceMgr is planned along with implementation of other "Packet
/// Filters" such as these supporting Linux Packet Filtering and
-/// Berkley Packet Filtering.
+/// Berkeley Packet Filtering.
class TestPktFilter : public PktFilter {
public:
@@ -210,7 +210,7 @@ public:
/// @brief Returns the collection of existing interfaces.
IfaceCollection& getIfacesLst() { return (ifaces_); }
- /// @brief This function creates fictitious interfaces with fictious
+ /// @brief This function creates fictitious interfaces with fictitious
/// addresses.
///
/// These interfaces can be used in tests that don't actually try
@@ -281,7 +281,7 @@ public:
}
/// @brief Checks if the specified interface has a socket bound to a
- /// specified adddress.
+ /// specified address.
///
/// @param iface_name A name of the interface.
/// @param addr An address to be checked for binding.
@@ -390,7 +390,7 @@ public:
#endif
}
- // Get ther number of IPv4 or IPv6 sockets on the loopback interface
+ // Get the number of IPv4 or IPv6 sockets on the loopback interface
int getOpenSocketsCount(const Iface& iface, uint16_t family) const {
// Get all sockets.
Iface::SocketCollection sockets = iface.getSockets();
@@ -641,10 +641,10 @@ TEST_F(IfaceMgrTest, getIface) {
// Interface name, ifindex
IfacePtr iface1(new Iface("lo1", 100));
IfacePtr iface2(new Iface("eth9", 101));
- IfacePtr iface3(new Iface("en3", 102));
+ IfacePtr iface3(new Iface("en7", 102));
IfacePtr iface4(new Iface("e1000g4", 103));
cout << "This test assumes that there are less than 100 network interfaces"
- << " in the tested system and there are no lo1, eth9, en3, e1000g4"
+ << " in the tested system and there are no lo1, eth9, en7, e1000g4"
<< " or wifi15 interfaces present." << endl;
// Note: real interfaces may be detected as well
@@ -666,7 +666,7 @@ TEST_F(IfaceMgrTest, getIface) {
IfacePtr tmp = ifacemgr->getIface(102);
ASSERT_TRUE(tmp);
- EXPECT_EQ("en3", tmp->getName());
+ EXPECT_EQ("en7", tmp->getName());
EXPECT_EQ(102, tmp->getIndex());
// Check that interface can be retrieved by name
@@ -729,7 +729,7 @@ TEST_F(IfaceMgrTest, receiveTimeout6) {
time_duration duration = stop_time - start_time;
// We stop the clock when the call completes so it does not
// precisely reflect the receive timeout. However the
- // uncertainity should be low enough to expect that measured
+ // uncertainty should be low enough to expect that measured
// value is in the range <1.4s; 1.7s>.
EXPECT_GE(duration.total_microseconds(),
1400000 - TIMEOUT_TOLERANCE);
@@ -781,7 +781,7 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
time_duration duration = stop_time - start_time;
// We stop the clock when the call completes so it does not
// precisely reflect the receive timeout. However the
- // uncertainity should be low enough to expect that measured
+ // uncertainty should be low enough to expect that measured
// value is in the range <2.3s; 2.6s>.
EXPECT_GE(duration.total_microseconds(),
2300000 - TIMEOUT_TOLERANCE);
@@ -833,7 +833,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
cout << "Local loopback interface not found. Skipping test. " << endl;
return;
}
- // Once sockets have been sucessfully opened, they are supposed to
+ // Once sockets have been successfully opened, they are supposed to
// be on the list. Here we start to test if all expected sockets
// are on the list and no other (unexpected) socket is there.
Iface::SocketCollection sockets = iface_ptr->getSockets();
@@ -874,7 +874,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
sockets = iface_ptr->getSockets();
ASSERT_EQ(0, sockets.size());
- // We are still in posession of socket descriptors that we created
+ // We are still in possession of socket descriptors that we created
// on the beginning of this test. We can use them to check whether
// closeSockets() only removed them from the list or they have been
// really closed.
@@ -1209,7 +1209,7 @@ TEST_F(IfaceMgrTest, sendReceive4) {
// skip checking source port of sent address.
// Close the socket. Further we will test if errors are reported
- // properly on attempt to use closed soscket.
+ // properly on attempt to use closed socket.
close(socket1);
// Warning: kernel bug on FreeBSD. The following code checks that attempt to
@@ -1226,7 +1226,7 @@ TEST_F(IfaceMgrTest, sendReceive4) {
//
// @todo: This part of the test is currently disabled on all BSD systems as it was
// the quick fix. We need a more elegant (config-based) solution to disable
-// this check on affected systems only. The ticket has been submited for this
+// this check on affected systems only. The ticket has been submitted for this
// work: http://kea.isc.org/ticket/2971
#ifndef OS_BSD
EXPECT_THROW(ifacemgr->receive4(10), SocketReadError);
@@ -1399,11 +1399,11 @@ TEST_F(IfaceMgrTest, checkPacketFilterRawSocket) {
// Note: This test will only run on non-Linux and non-BSD systems.
// This test checks whether it is possible to use IfaceMgr to figure
-// out which Pakcket Filter object should be used when direct responses
+// out which Packet Filter object should be used when direct responses
// to hosts, having no address assigned are desired or not desired.
// Since direct responses aren't supported on systems other than Linux
// and BSD the function under test should always set object of
-// PktFilterInet type as current Packet Filter. This object does not
+// PktFilterInet type as current Packet Filter. This object does not
//support direct responses. Once implementation is added on systems
// other than BSD and Linux the OS specific version of the test will
// be removed.
@@ -1471,7 +1471,7 @@ TEST_F(IfaceMgrTest, openSockets4) {
ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
// Simulate opening sockets using the dummy packet filter.
- ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
+ ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0));
// Expect that the sockets are open on both eth0 and eth1.
EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
@@ -1538,7 +1538,7 @@ TEST_F(IfaceMgrTest, openSockets4IfaceInactive) {
// - is inactive
ifacemgr.setIfaceFlags("eth1", false, true, true, true, false);
ASSERT_TRUE(ifacemgr.getIface("eth1")->inactive4_);
- ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
+ ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0));
// The socket on eth0 should be open because interface is up, running and
// active (not disabled through DHCP configuration, for example).
@@ -1569,7 +1569,7 @@ TEST_F(IfaceMgrTest, openSockets4NoErrorHandler) {
// The function throws an exception when it tries to open a socket
// and bind it to the address in use.
- EXPECT_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL),
+ EXPECT_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0),
isc::dhcp::SocketConfigError);
}
@@ -1599,7 +1599,7 @@ TEST_F(IfaceMgrTest, openSocket4ErrorHandler) {
// open and bound to the same address and port. An attempt to open
// another socket and bind to this address and port should fail.
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler));
- // We expect that an error occured when we tried to open a socket on
+ // We expect that an error occurred when we tried to open a socket on
// eth0, but the socket on eth1 should open just fine.
EXPECT_EQ(1, errors_count_);
@@ -1629,7 +1629,7 @@ TEST_F(IfaceMgrTest, hasOpenSocketForAddress4) {
ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
// Simulate opening sockets using the dummy packet filter.
- ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
+ ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0));
// Expect that the sockets are open on both eth0 and eth1.
ASSERT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
@@ -1712,7 +1712,7 @@ TEST_F(IfaceMgrTest, openSockets6NoLinkLocal) {
// Check that the number of sockets is correct on each interface.
checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
- // The thrid parameter specifies that the number of link-local
+ // The third parameter specifies that the number of link-local
// addresses for eth0 is equal to 0.
checkSocketsCount6(*ifacemgr.getIface("eth0"), 0, 0);
checkSocketsCount6(*ifacemgr.getIface("eth1"), 0, 1);
@@ -1731,7 +1731,7 @@ TEST_F(IfaceMgrTest, openSockets6NoLinkLocal) {
}
-// This test checks that socket is open on the non-muticast-capable
+// This test checks that socket is open on the non-multicast-capable
// interface. This socket simply doesn't join multicast group.
TEST_F(IfaceMgrTest, openSockets6NotMulticast) {
NakedIfaceMgr ifacemgr;
@@ -2014,7 +2014,7 @@ TEST_F(IfaceMgrTest, openSocket6ErrorHandler) {
// opened on eth0 and an attempt to open another socket and bind to
// the same address and port should fail.
ASSERT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, error_handler));
- // We expect that an error occured when we tried to open a socket on
+ // We expect that an error occurred when we tried to open a socket on
// eth0, but the socket on eth1 should open just fine.
EXPECT_EQ(1, errors_count_);
@@ -2124,7 +2124,7 @@ TEST_F(IfaceMgrTest, iface_methods) {
OutOfRange
);
- // MAC length should stay not set as excep
+ // MAC length should stay not set as exception was thrown.
EXPECT_EQ(0, iface.getMacLen());
// Setting maximum length MAC should be ok.
@@ -2387,7 +2387,7 @@ TEST_F(IfaceMgrTest, detectIfaces) {
NakedIfaceMgr ifacemgr;
// We are using struct ifaddrs as it is the only good portable one
- // ifreq and ioctls are far from portabe. For BSD ifreq::ifa_flags field
+ // ifreq and ioctls are far from portable. For BSD ifreq::ifa_flags field
// is only a short which, nowadays, can be negative
struct ifaddrs *iflist = 0, *ifptr = 0;
ASSERT_EQ(0, getifaddrs(&iflist))
@@ -2493,7 +2493,7 @@ TEST_F(IfaceMgrTest, SingleExternalSocket4) {
// Tests if multiple external sockets and their callbacks can be passed and
// it is supported properly by receive4() method.
-TEST_F(IfaceMgrTest, MiltipleExternalSockets4) {
+TEST_F(IfaceMgrTest, MultipleExternalSockets4) {
callback_ok = false;
callback2_ok = false;
@@ -2662,7 +2662,7 @@ TEST_F(IfaceMgrTest, SingleExternalSocket6) {
// Tests if multiple external sockets and their callbacks can be passed and
// it is supported properly by receive6() method.
-TEST_F(IfaceMgrTest, MiltipleExternalSockets6) {
+TEST_F(IfaceMgrTest, MultipleExternalSockets6) {
callback_ok = false;
callback2_ok = false;
@@ -2792,7 +2792,7 @@ TEST_F(IfaceMgrTest, DeleteExternalSockets6) {
// Test checks if the unicast sockets can be opened.
// This test is now disabled, because there is no reliable way to test it. We
-// can't even use loopback, beacuse openSockets() skips loopback interface
+// can't even use loopback, because openSockets() skips loopback interface
// (as it should be, because DHCP server is not supposed to listen on loopback).
TEST_F(IfaceMgrTest, DISABLED_openUnicastSockets) {
/// @todo Need to implement a test that is able to check whether we can open
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index 50fa4e3a82..af11a53bcc 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -329,7 +329,7 @@ TEST_F(LibDhcpTest, optionFactory) {
OptionBuffer buf;
// Factory functions for specific options must be registered before
// they can be used to create options instances. Otherwise exception
- // is rised.
+ // is raised.
EXPECT_THROW(LibDHCP::optionFactory(Option::V4, DHO_SUBNET_MASK, buf),
isc::BadValue);
@@ -490,7 +490,7 @@ TEST_F(LibDhcpTest, unpackOptions6) {
ASSERT_TRUE(opt_oro);
// Get set of uint16_t values.
std::vector<uint16_t> opts = opt_oro->getValues();
- // Prepare the refrence data.
+ // Prepare the reference data.
std::vector<uint16_t> expected_opts;
expected_opts.push_back(0x6C6D); // equivalent to: 108, 109
expected_opts.push_back(0x6E6F); // equivalent to 110, 111
@@ -718,7 +718,7 @@ TEST_F(LibDhcpTest, packOptions4) {
// we want to use this buffer as a reference to verify that produced
// option in on-wire format is correct.
- // Create Ciruit ID sub-option and add to RAI.
+ // Create Circuit ID sub-option and add to RAI.
OptionPtr circuit_id(new Option(Option::V4, RAI_OPTION_AGENT_CIRCUIT_ID,
OptionBuffer(v4_opts + 46,
v4_opts + 50)));
@@ -736,7 +736,7 @@ TEST_F(LibDhcpTest, packOptions4) {
isc::dhcp::OptionCollection opts; // list of options
// Note that we insert each option under the same option code into
- // the map. This gurantees that options are packed in the same order
+ // the map. This guarantees that options are packed in the same order
// they were added. Otherwise, options would get sorted by code and
// the resulting buffer wouldn't match with the reference buffer.
opts.insert(make_pair(opt1->getType(), opt1));
@@ -1319,8 +1319,21 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
LibDhcpTest::testStdOptionDefs4(DHO_UUID_GUID, begin, begin + 17,
typeid(OptionCustom));
- LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, begin, end,
- typeid(Option));
+ // Prepare buffer holding an array of FQDNs.
+ const char fqdn_data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+ // Initialize a vector with the FQDN data.
+ std::vector<uint8_t> fqdn_buf(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(),
+ fqdn_buf.end(), typeid(OptionCustom));
// V-I Vendor option requires specially crafted data.
const char vivco_data[] = {
@@ -1588,31 +1601,6 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
LibDhcpTest::testStdOptionDefs6(D6O_TIMESTAMP, begin, begin + 8,
typeid(Option));
- // RFC7598 options
- LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_RULE, begin, end,
- typeid(OptionCustom), "s46-rule-options");
- LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_RULE, begin, end,
- typeid(OptionCustom), "s46-rule-options");
- LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_BR, begin, end,
- typeid(OptionCustom));
- LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_BR, begin, end,
- typeid(OptionCustom));
- LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_DMR, begin, end,
- typeid(OptionCustom));
- LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_V4V6BIND, begin,
- end, typeid(OptionCustom),
- "s46-v4v6bind-options");
- LibDhcpTest::testOptionDefs6(V4V6_RULE_OPTION_SPACE, D6O_S46_PORTPARAMS,
- begin, end, typeid(OptionCustom), "");
- LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPE, begin, end,
- typeid(OptionCustom),
- "s46-cont-mape-options");
- LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPT, begin, end,
- typeid(OptionCustom),
- "s46-cont-mapt-options");
- LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_LW, begin, end,
- typeid(OptionCustom),
- "s46-cont-lw-options");
}
@@ -1679,6 +1667,124 @@ TEST_F(LibDhcpTest, getVendorOptionDefByName4) {
}
}
+// This test checks handling of uncompressed FQDN list.
+TEST_F(LibDhcpTest, fqdnList) {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DOMAIN_SEARCH);
+ ASSERT_TRUE(def);
+
+ // Prepare buffer holding an array of FQDNs.
+ const uint8_t fqdn[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+ /* This size is used later so protect ourselves against changes */
+ static_assert(sizeof(fqdn) == 40,
+ "incorrect uncompressed domain list size");
+ // Initialize a vector with the FQDN data.
+ std::vector<uint8_t> fqdn_buf(fqdn, fqdn + sizeof(fqdn));
+
+ OptionPtr option;
+ ASSERT_NO_THROW(option = def->optionFactory(Option::V4,
+ DHO_DOMAIN_SEARCH,
+ fqdn_buf.begin(),
+ fqdn_buf.end()));
+ ASSERT_TRUE(option);
+ OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_TRUE(names);
+ EXPECT_EQ(sizeof(fqdn), names->len() - names->getHeaderLen());
+ ASSERT_EQ(3, names->getDataFieldsNum());
+ EXPECT_EQ("mydomain.example.com.", names->readFqdn(0));
+ EXPECT_EQ("example.com.", names->readFqdn(1));
+ EXPECT_EQ("com.", names->readFqdn(2));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(),
+ fqdn_buf.end(), typeid(OptionCustom));
+}
+
+// This test checks handling of compressed FQDN list.
+// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual
+// compression algorithm).
+TEST_F(LibDhcpTest, fqdnListCompressed) {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DOMAIN_SEARCH);
+ ASSERT_TRUE(def);
+
+ const uint8_t compressed[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 192, 9, // pointer to example.com
+ 192, 17 // pointer to com
+ };
+ std::vector<uint8_t> compressed_buf(compressed,
+ compressed + sizeof(compressed));
+ OptionPtr option;
+ ASSERT_NO_THROW(option = def->optionFactory(Option::V4,
+ DHO_DOMAIN_SEARCH,
+ compressed_buf.begin(),
+ compressed_buf.end()));
+ ASSERT_TRUE(option);
+ OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_TRUE(names);
+ /* Use the uncompress length here (cf fqdnList) */
+ EXPECT_EQ(40, names->len() - names->getHeaderLen());
+ ASSERT_EQ(3, names->getDataFieldsNum());
+ EXPECT_EQ("mydomain.example.com.", names->readFqdn(0));
+ EXPECT_EQ("example.com.", names->readFqdn(1));
+ EXPECT_EQ("com.", names->readFqdn(2));
+}
+
+// Check that incorrect FQDN list compression is rejected.
+// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual
+// compression algorithm).
+TEST_F(LibDhcpTest, fqdnListBad) {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DOMAIN_SEARCH);
+ ASSERT_TRUE(def);
+
+ const uint8_t bad[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 192, 80, // too big/forward pointer
+ 192, 11 // pointer to com
+ };
+ std::vector<uint8_t> bad_buf(bad, bad + sizeof(bad));
+
+ OptionPtr option;
+ EXPECT_THROW(option = def->optionFactory(Option::V4,
+ DHO_DOMAIN_SEARCH,
+ bad_buf.begin(),
+ bad_buf.end()),
+ InvalidOptionValue);
+}
+
+// Check that empty (truncated) option is rejected.
+TEST_F(LibDhcpTest, fqdnListTrunc) {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DOMAIN_SEARCH);
+ ASSERT_TRUE(def);
+
+ std::vector<uint8_t> empty;
+
+ OptionPtr option;
+ EXPECT_THROW(option = def->optionFactory(Option::V4,
+ DHO_DOMAIN_SEARCH,
+ empty.begin(),
+ empty.end()),
+ InvalidOptionValue);
+}
+
// tests whether v6 vendor-class option can be parsed properly.
TEST_F(LibDhcpTest, vendorClass6) {
diff --git a/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
index 474da98b51..85cbe868b2 100644
--- a/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
+++ b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -386,7 +386,7 @@ TEST(OpaqueDataTuple, unpack1ByteZeroLength) {
EXPECT_EQ(0, tuple.getLength());
}
-// This test verfifies that exception is thrown if the empty buffer is being
+// This test verifies that exception is thrown if the empty buffer is being
// parsed.
TEST(OpaqueDataTuple, unpack1ByteEmptyBuffer) {
OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
@@ -417,7 +417,7 @@ TEST(OpaqueDataTuple, unpack2Byte) {
for (int i = 0; i < 400; ++i) {
wire_data.push_back(i);
}
- // The unpack shoud succeed.
+ // The unpack should succeed.
ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
// The decoded length should be 400.
ASSERT_EQ(400, tuple.getLength());
diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc
index e2a7cc9823..53d121f9a5 100644
--- a/src/lib/dhcp/tests/option6_ia_unittest.cc
+++ b/src/lib/dhcp/tests/option6_ia_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -80,7 +80,7 @@ public:
EXPECT_EQ(12, opt->len() - opt->getHeaderLen());
EXPECT_EQ(type, opt->getType());
- EXPECT_EQ(16, outBuf_.getLength()); // lenght(IA_NA) = 16
+ EXPECT_EQ(16, outBuf_.getLength()); // length(IA_NA) = 16
// Check if pack worked properly:
InputBuffer out(outBuf_.getData(), outBuf_.getLength());
diff --git a/src/lib/dhcp/tests/option6_iaprefix_unittest.cc b/src/lib/dhcp/tests/option6_iaprefix_unittest.cc
index c2694420c0..5e86220640 100644
--- a/src/lib/dhcp/tests/option6_iaprefix_unittest.cc
+++ b/src/lib/dhcp/tests/option6_iaprefix_unittest.cc
@@ -101,7 +101,7 @@ public:
/// @brief Checks whether content of output buffer is correct
///
- /// Output buffer is expected to be filled with an option matchin
+ /// Output buffer is expected to be filled with an option matching
/// buf_ content as defined in setExampleBuffer().
///
/// @param expected_type expected option type
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index f897958ff8..5a44bd908f 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -99,8 +99,24 @@ public:
/// @tparam integer type.
template<typename T>
void writeInt(T value, std::vector<uint8_t>& buf) {
- for (int i = 0; i < sizeof(T); ++i) {
- buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+ switch (sizeof(T)) {
+ case 4:
+ buf.push_back((value >> 24) & 0xFF);
+ /* falls into */
+ case 3:
+ buf.push_back((value >> 16) & 0xFF);
+ /* falls into */
+ case 2:
+ buf.push_back((value >> 8) & 0xFF);
+ /* falls into */
+ case 1:
+ buf.push_back(value & 0xFF);
+ break;
+ default:
+ // This loop is incorrectly compiled by some old g++?!
+ for (int i = 0; i < sizeof(T); ++i) {
+ buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+ }
}
}
@@ -282,6 +298,109 @@ TEST_F(OptionCustomTest, booleanData) {
}
// The purpose of this test is to verify that the data from a buffer
+// can be read as a DHCPv4 tuple.
+TEST_F(OptionCustomTest, tupleData4) {
+ OptionDefinition opt_def("option-foo", 232, "tuple", "option-foo-space");
+
+ const char data[] = {
+ 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Append suboption. It should be present in the parsed packet.
+ appendV4Suboption(buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Check it
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("foobar", value);
+
+ // Now as a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ EXPECT_NO_THROW(option->readTuple(tuple, 0));
+ EXPECT_EQ("foobar", tuple.getText());
+
+ // There should be one suboption present.
+ EXPECT_TRUE(hasV4Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4,
+ buf.begin(), buf.begin() + 6)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Check that the option with "no data" is rejected.
+ buf.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4,
+ buf.begin(), buf.end())),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the data from a buffer
+// can be read as a DHCPv6 tuple.
+TEST_F(OptionCustomTest, tupleData6) {
+ OptionDefinition opt_def("option-foo", 1000, "tuple", "option-foo-space");
+
+ const char data[] = {
+ 0, 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Append suboption. It should be present in the parsed packet.
+ appendV6Suboption(buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Check it
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("foobar", value);
+
+ // Now as a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ EXPECT_NO_THROW(option->readTuple(tuple, 0));
+ EXPECT_EQ("foobar", tuple.getText());
+
+ // There should be one suboption present.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 1)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 7)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+}
+
+// The purpose of this test is to verify that the data from a buffer
// can be read as FQDN.
TEST_F(OptionCustomTest, fqdnData) {
OptionDefinition opt_def("option-foo", 1000, "fqdn", "option-foo-space");
@@ -940,7 +1059,97 @@ TEST_F(OptionCustomTest, psidDataArray) {
EXPECT_EQ(0x01, psid2.second.asUint16());
}
-// The purpose of this test is to verify that the opton definition comprising
+// The purpose of this test is to verify that the data from a buffer
+// can be read as DHCPv4 tuples.
+TEST_F(OptionCustomTest, tupleDataArray4) {
+ OptionDefinition opt_def("option-foo", 232, "tuple", true);
+
+ const char data[] = {
+ 5, 104, 101, 108, 108, 111, // "hello"
+ 1, 32, // " "
+ 5, 119, 111, 114, 108, 100 // "world"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check them
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("hello", value);
+ ASSERT_NO_THROW(value = option->readTuple(1));
+ EXPECT_EQ(" ", value);
+ ASSERT_NO_THROW(value = option->readTuple(2));
+ EXPECT_EQ("world", value);
+
+ // There should be no suboption present.
+ EXPECT_FALSE(hasV4Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4,
+ buf.begin(), buf.begin() + 12)),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the data from a buffer
+// can be read as DHCPv6 tuples.
+TEST_F(OptionCustomTest, tupleDataArray6) {
+ OptionDefinition opt_def("option-foo", 1000, "tuple", true);
+
+ const char data[] = {
+ 0, 5, 104, 101, 108, 108, 111, // "hello"
+ 0, 1, 32, // " "
+ 0, 5, 119, 111, 114, 108, 100 // "world"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check them
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("hello", value);
+ ASSERT_NO_THROW(value = option->readTuple(1));
+ EXPECT_EQ(" ", value);
+ ASSERT_NO_THROW(value = option->readTuple(2));
+ EXPECT_EQ("world", value);
+
+ // There should be no suboption present.
+ EXPECT_FALSE(hasV6Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 8)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 16)),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
// a record of fixed-size fields can be used to create an option with a
// suboption.
TEST_F(OptionCustomTest, recordDataWithSuboption) {
@@ -1148,7 +1357,7 @@ TEST_F(OptionCustomTest, setBinaryData) {
}
// Try to override the default binary buffer.
ASSERT_NO_THROW(option->writeBinary(buf_in));
- // And check that it has been actually overriden.
+ // And check that it has been actually overridden.
ASSERT_NO_THROW(buf = option->readBinary());
ASSERT_EQ(buf_in.size(), buf.size());
EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf.begin()));
@@ -1156,7 +1365,7 @@ TEST_F(OptionCustomTest, setBinaryData) {
// The purpose of this test is to verify that an option comprising
// single boolean data field can be created and that its default
-// value can be overriden by a new value.
+// value can be overridden by a new value.
TEST_F(OptionCustomTest, setBooleanData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
@@ -1173,13 +1382,13 @@ TEST_F(OptionCustomTest, setBooleanData) {
EXPECT_FALSE(value);
// Check that we can override the default value.
ASSERT_NO_THROW(option->writeBoolean(true));
- // Finally, check that it has been actually overriden.
+ // Finally, check that it has been actually overridden.
ASSERT_NO_THROW(value = option->readBoolean());
EXPECT_TRUE(value);
}
/// The purpose of this test is to verify that the data field value
-/// can be overriden by a new value.
+/// can be overridden by a new value.
TEST_F(OptionCustomTest, setUint32Data) {
// Create a definition of an option that holds single
// uint32 value.
@@ -1209,7 +1418,7 @@ TEST_F(OptionCustomTest, setUint32Data) {
// The purpose of this test is to verify that an option comprising
// single IPv4 address can be created and that this address can
-// be overriden by a new value.
+// be overridden by a new value.
TEST_F(OptionCustomTest, setIpv4AddressData) {
OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address");
@@ -1231,9 +1440,9 @@ TEST_F(OptionCustomTest, setIpv4AddressData) {
EXPECT_EQ("192.168.0.1", address.toText());
}
-// The purpose of this test is to verify that an opton comprising
+// The purpose of this test is to verify that an option comprising
// single IPv6 address can be created and that this address can
-// be overriden by a new value.
+// be overridden by a new value.
TEST_F(OptionCustomTest, setIpv6AddressData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
@@ -1256,7 +1465,7 @@ TEST_F(OptionCustomTest, setIpv6AddressData) {
}
// The purpose of this test is to verify that an option comprising
-// a prefix can be created and that the prefix can be overriden by
+// a prefix can be created and that the prefix can be overridden by
// a new value.
TEST_F(OptionCustomTest, setPrefixData) {
OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix");
@@ -1285,7 +1494,7 @@ TEST_F(OptionCustomTest, setPrefixData) {
}
// The purpose of this test is to verify that an option comprising
-// a single PSID can be created and that the PSID can be overriden
+// a single PSID can be created and that the PSID can be overridden
// by a new value.
TEST_F(OptionCustomTest, setPsidData) {
OptionDefinition opt_def("option-foo", 1000, "psid");
@@ -1342,7 +1551,7 @@ TEST_F(OptionCustomTest, setStringData) {
/// The purpose of this test is to verify that an option comprising
/// a default FQDN value can be created and that this value can be
-/// overriden after the option has been created.
+/// overridden after the option has been created.
TEST_F(OptionCustomTest, setFqdnData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
@@ -1359,7 +1568,7 @@ TEST_F(OptionCustomTest, setFqdnData) {
EXPECT_EQ(".", fqdn);
// Try override the default FQDN value.
ASSERT_NO_THROW(option->writeFqdn("example.com"));
- // Check that the value has been actually overriden.
+ // Check that the value has been actually overridden.
ASSERT_NO_THROW(fqdn = option->readFqdn());
EXPECT_EQ("example.com.", fqdn);
}
@@ -1610,6 +1819,96 @@ TEST_F(OptionCustomTest, setIPv6PrefixDataArray) {
});
}
+/// The purpose of this test is to verify that an option comprising an
+/// array of DHCPv4 tuples can be created with no tuples and that
+/// tuples can be later added after the option has been created.
+TEST_F(OptionCustomTest, setTupleDataArray4) {
+ OptionDefinition opt_def("option-foo", 232, "tuple", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new DHCPv4 tuple into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(std::string("hello")));
+ ASSERT_NO_THROW(option->addArrayDataField(std::string(" ")));
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple.append("world");
+ ASSERT_NO_THROW(option->addArrayDataField(tuple));
+
+ // We should have now 3 tuples added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(0);
+ EXPECT_EQ("hello", value);
+ });
+
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(1);
+ EXPECT_EQ(" ", value);
+ });
+
+ ASSERT_NO_THROW({
+ OpaqueDataTuple value(OpaqueDataTuple::LENGTH_1_BYTE);
+ option->readTuple(value, 2);
+ EXPECT_EQ("world", value.getText());
+ });
+}
+
+/// The purpose of this test is to verify that an option comprising an
+/// array of DHCPv6 tuples can be created with no tuples and that
+/// tuples can be later added after the option has been created.
+TEST_F(OptionCustomTest, setTupleDataArray6) {
+ OptionDefinition opt_def("option-foo", 1000, "tuple", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new DHCPv6 tuple into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(std::string("hello")));
+ ASSERT_NO_THROW(option->addArrayDataField(std::string(" ")));
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple.append("world");
+ ASSERT_NO_THROW(option->addArrayDataField(tuple));
+
+ // We should have now 3 tuples added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(0);
+ EXPECT_EQ("hello", value);
+ });
+
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(1);
+ EXPECT_EQ(" ", value);
+ });
+
+ ASSERT_NO_THROW({
+ OpaqueDataTuple value(OpaqueDataTuple::LENGTH_2_BYTES);
+ option->readTuple(value, 2);
+ EXPECT_EQ("world", value.getText());
+ });
+}
+
TEST_F(OptionCustomTest, setRecordData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
@@ -1620,6 +1919,7 @@ TEST_F(OptionCustomTest, setRecordData) {
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
ASSERT_NO_THROW(opt_def.addRecordField("psid"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix"));
+ ASSERT_NO_THROW(opt_def.addRecordField("tuple"));
ASSERT_NO_THROW(opt_def.addRecordField("string"));
// Create an option and let the data field be initialized
@@ -1632,7 +1932,7 @@ TEST_F(OptionCustomTest, setRecordData) {
// The number of elements should be equal to number of elements
// in the record.
- ASSERT_EQ(8, option->getDataFieldsNum());
+ ASSERT_EQ(9, option->getDataFieldsNum());
// Check that the default values have been correctly set.
uint16_t value0;
@@ -1658,9 +1958,12 @@ TEST_F(OptionCustomTest, setRecordData) {
ASSERT_NO_THROW(value6 = option->readPrefix(6));
EXPECT_EQ(0, value6.first.asUnsigned());
EXPECT_EQ("::", value6.second.toText());
- std::string value7 = "xyz";
- ASSERT_NO_THROW(value7 = option->readString(7));
- EXPECT_TRUE(value7.empty());
+ std::string value7 = "abc";
+ // Tuple has no default value
+ EXPECT_THROW(option->readTuple(7), BadDataTypeCast);
+ std::string value8 = "xyz";
+ ASSERT_NO_THROW(value8 = option->readString(8));
+ EXPECT_TRUE(value8.empty());
// Override each value with a new value.
ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
@@ -1671,7 +1974,8 @@ TEST_F(OptionCustomTest, setRecordData) {
ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5));
ASSERT_NO_THROW(option->writePrefix(PrefixLen(48),
IOAddress("2001:db8:1::"), 6));
- ASSERT_NO_THROW(option->writeString("hello world", 7));
+ ASSERT_NO_THROW(option->writeTuple("foobar", 7));
+ ASSERT_NO_THROW(option->writeString("hello world", 8));
// Check that the new values have been correctly set.
ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
@@ -1690,8 +1994,10 @@ TEST_F(OptionCustomTest, setRecordData) {
ASSERT_NO_THROW(value6 = option->readPrefix(6));
EXPECT_EQ(48, value6.first.asUnsigned());
EXPECT_EQ("2001:db8:1::", value6.second.toText());
- ASSERT_NO_THROW(value7 = option->readString(7));
- EXPECT_EQ(value7, "hello world");
+ ASSERT_NO_THROW(value7 = option->readTuple(7));
+ EXPECT_EQ(value7, "foobar");
+ ASSERT_NO_THROW(value8 = option->readString(8));
+ EXPECT_EQ(value8, "hello world");
}
// The purpose of this test is to verify that pack function for
diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc
index fae60eb82a..71af5d3167 100644
--- a/src/lib/dhcp/tests/option_data_types_unittest.cc
+++ b/src/lib/dhcp/tests/option_data_types_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -44,8 +44,24 @@ public:
/// @tparam integer type.
template<typename T>
void writeInt(T value, std::vector<uint8_t>& buf) {
- for (int i = 0; i < sizeof(T); ++i) {
- buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+ switch (sizeof(T)) {
+ case 4:
+ buf.push_back((value >> 24) & 0xFF);
+ /* falls into */
+ case 3:
+ buf.push_back((value >> 16) & 0xFF);
+ /* falls into */
+ case 2:
+ buf.push_back((value >> 8) & 0xFF);
+ /* falls into */
+ case 1:
+ buf.push_back(value & 0xFF);
+ break;
+ default:
+ // This loop is incorrectly compiled by some old g++?!
+ for (int i = 0; i < sizeof(T); ++i) {
+ buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+ }
}
}
@@ -176,6 +192,119 @@ TEST_F(OptionDataTypesTest, writeBinary) {
EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
}
+// The purpose of this test is to verify that the tuple value stored
+TEST_F(OptionDataTypesTest, readTuple) {
+ // The string
+ std::string value = "hello world";
+ // Create an input buffer.
+ std::vector<uint8_t> buf;
+ // DHCPv4 tuples use 1 byte length
+ writeInt<uint8_t>(static_cast<uint8_t>(value.size()), buf);
+ writeString(value, buf);
+
+ // Read the string from the buffer.
+ std::string result;
+ ASSERT_NO_THROW(
+ result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_1_BYTE);
+ );
+ // Check that it is valid.
+ EXPECT_EQ(value, result);
+
+ // Read the tuple from the buffer.
+ OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple4));
+ // Check that it is valid.
+ EXPECT_EQ(value, tuple4.getText());
+
+ buf.clear();
+
+ // DHCPv6 tuples use 2 byte length
+ writeInt<uint16_t>(static_cast<uint16_t>(value.size()), buf);
+ writeString(value, buf);
+
+ // Read the string from the buffer.
+ ASSERT_NO_THROW(
+ result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_2_BYTES);
+ );
+ // Check that it is valid.
+ EXPECT_EQ(value, result);
+
+ // Read the tuple from the buffer.
+ OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple6));
+ // Check that it is valid.
+ EXPECT_EQ(value, tuple6.getText());
+}
+
+// The purpose of this test is to verify that a tuple value
+// are correctly encoded in a buffer (string version)
+TEST_F(OptionDataTypesTest, writeTupleString) {
+ // The string
+ std::string value = "hello world";
+ // Create an output buffer.
+ std::vector<uint8_t> buf;
+
+ // Encode it in DHCPv4
+ OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_1_BYTE, buf);
+
+ // Check that it is valid.
+ ASSERT_EQ(value.size() + 1, buf.size());
+ std::vector<uint8_t> expected;
+ writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected);
+ writeString(value, expected);
+ EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
+
+ buf.clear();
+
+ // Encode it in DHCPv6
+ OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_2_BYTES, buf);
+
+ // Check that it is valid.
+ ASSERT_EQ(value.size() + 2, buf.size());
+ expected.clear();
+ writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected);
+ writeString(value, expected);
+ EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
+}
+
+// The purpose of this test is to verify that a tuple value
+// are correctly encoded in a buffer (tuple version)
+TEST_F(OptionDataTypesTest, writeTuple) {
+ // The string
+ std::string value = "hello world";
+ // Create a DHCPv4 tuple
+ OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple4.append(value);
+ // Create an output buffer.
+ std::vector<uint8_t> buf;
+
+ // Encode it in DHCPv4
+ OptionDataTypeUtil::writeTuple(tuple4, buf);
+
+ // Check that it is valid.
+ ASSERT_EQ(value.size() + 1, buf.size());
+ std::vector<uint8_t> expected;
+ writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected);
+ writeString(value, expected);
+ EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
+
+ buf.clear();
+
+ // Create a DHCPv6 tuple
+ OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple6.append(value);
+
+ // Encode it in DHCPv6
+ OptionDataTypeUtil::writeTuple(tuple6, buf);
+
+ // Check that it is valid.
+ ASSERT_EQ(value.size() + 2, buf.size());
+ expected.clear();
+ writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected);
+ writeString(value, expected);
+ EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
+}
+
// The purpose of this test is to verify that the boolean value stored
// in a buffer is correctly read from this buffer.
TEST_F(OptionDataTypesTest, readBool) {
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index 03ce31f61d..8b7f827218 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -18,6 +18,7 @@
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_string.h>
+#include <dhcp/option_opaque_data_tuples.h>
#include <exceptions/exceptions.h>
#include <boost/pointer_cast.hpp>
@@ -137,7 +138,7 @@ TEST_F(OptionDefinitionTest, copyConstructor) {
EXPECT_EQ("isc", opt_def_copy2.getEncapsulatedSpace());
}
-// This test checks that two option definitions may be compared fot equality.
+// This test checks that two option definitions may be compared for equality.
TEST_F(OptionDefinitionTest, equality) {
// Equal definitions.
EXPECT_TRUE(OptionDefinition("option-foo", 5, "uint16", false)
@@ -290,7 +291,7 @@ TEST_F(OptionDefinitionTest, validate) {
OptionDefinition opt_def9("option-clientid", D6O_CLIENTID, "string");
EXPECT_NO_THROW(opt_def9.validate());
- // Using hyphen or undescore at the beginning or at the end
+ // Using hyphen or underscore at the beginning or at the end
// of the option name is not allowed.
OptionDefinition opt_def10("-option-clientid", D6O_CLIENTID, "string");
EXPECT_THROW(opt_def10.validate(), MalformedOptionDefinition);
@@ -741,7 +742,7 @@ TEST_F(OptionDefinitionTest, recordIAAddr6) {
// The purpose of this test is to verify that definition can be created
// for option that comprises record of data. In this particular test
-// the IAADDR option is used. The data for the option is speicifed as
+// the IAADDR option is used. The data for the option is specified as
// a vector of strings. Each string carries the data for the corresponding
// data field.
TEST_F(OptionDefinitionTest, recordIAAddr6Tokenized) {
@@ -1190,7 +1191,8 @@ TEST_F(OptionDefinitionTest, uint32ArrayTokenized) {
OptionPtr option_v6;
std::vector<std::string> str_values;
str_values.push_back("123456");
- str_values.push_back("7");
+ // Try with hexadecimal
+ str_values.push_back("0x7");
str_values.push_back("256");
str_values.push_back("1111");
@@ -1510,4 +1512,210 @@ TEST_F(OptionDefinitionTest, psidArrayTokenized) {
EXPECT_EQ(7, psid2.second.asUint16());
}
+// This test verifies that a definition of an option with a single DHCPv4
+// tuple can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, tuple4) {
+ OptionDefinition opt_def("option-tuple", 232, "tuple");
+
+ OptionPtr option;
+
+ // Create a buffer holding tuple
+ const char data[] = {
+ 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+ OptionBuffer buf(data, data + sizeof(data));
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V4, 232, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with a single DHCPv6
+// tuple can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, tuple6) {
+ OptionDefinition opt_def("option-tuple", 1000, "tuple");
+
+ OptionPtr option;
+
+ // Create a buffer holding tuple
+ const char data[] = {
+ 0, 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+ OptionBuffer buf(data, data + sizeof(data));
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V6, 1000, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with a single DHCPv4
+// tuple can be created and that the instance of this option can be
+// created by specifying tuple value in the textual format.
+TEST_F(OptionDefinitionTest, tuple4Tokenized) {
+ OptionDefinition opt_def("option-tuple", 232, "tuple");
+
+ OptionPtr option;
+ // Specify a single tuple with "foobar" content.
+ std::vector<std::string> values(1, "foobar");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V4, 232, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with a single DHCPv6
+// tuple can be created and that the instance of this option can be
+// created by specifying tuple value in the textual format.
+TEST_F(OptionDefinitionTest, tuple6Tokenized) {
+ OptionDefinition opt_def("option-tuple", 1000, "tuple");
+
+ OptionPtr option;
+ // Specify a single tuple with "foobar" content.
+ std::vector<std::string> values(1, "foobar");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with an array
+// of DHCPv4 tuples can be created and that the instance of this option
+// can be created by specifying multiple DHCPv4 tuples in the textual format.
+TEST_F(OptionDefinitionTest, tuple4ArrayTokenized) {
+ OptionDefinition opt_def("option-tuple", 232, "tuple", true);
+
+ OptionPtr option;
+
+ // Specify 3 tuples.
+ std::vector<std::string> values;
+ values.push_back("hello");
+ values.push_back("the");
+ values.push_back("world");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V4, 232, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples));
+
+ // Validate the value.
+ OptionOpaqueDataTuplesPtr option_cast =
+ boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option);
+
+ // There should be 3 tuples in this option.
+ ASSERT_EQ(3, option_cast->getTuplesNum());
+
+ // Check their values.
+ OpaqueDataTuple tuple0 = option_cast->getTuple(0);
+ EXPECT_EQ("hello", tuple0.getText());
+
+ OpaqueDataTuple tuple1 = option_cast->getTuple(1);
+ EXPECT_EQ("the", tuple1.getText());
+
+ OpaqueDataTuple tuple2 = option_cast->getTuple(2);
+ EXPECT_EQ("world", tuple2.getText());
+}
+
+// This test verifies that a definition of an option with an array
+// of DHCPv6 tuples can be created and that the instance of this option
+// can be created by specifying multiple DHCPv6 tuples in the textual format.
+TEST_F(OptionDefinitionTest, tuple6ArrayTokenized) {
+ OptionDefinition opt_def("option-tuple", 1000, "tuple", true);
+
+ OptionPtr option;
+
+ // Specify 3 tuples.
+ std::vector<std::string> values;
+ values.push_back("hello");
+ values.push_back("the");
+ values.push_back("world");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples));
+
+ // Validate the value.
+ OptionOpaqueDataTuplesPtr option_cast =
+ boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option);
+
+ // There should be 3 tuples in this option.
+ ASSERT_EQ(3, option_cast->getTuplesNum());
+
+ // Check their values.
+ OpaqueDataTuple tuple0 = option_cast->getTuple(0);
+ EXPECT_EQ("hello", tuple0.getText());
+
+ OpaqueDataTuple tuple1 = option_cast->getTuple(1);
+ EXPECT_EQ("the", tuple1.getText());
+
+ OpaqueDataTuple tuple2 = option_cast->getTuple(2);
+ EXPECT_EQ("world", tuple2.getText());
+}
+
} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_int_unittest.cc b/src/lib/dhcp/tests/option_int_unittest.cc
index b9ff8aa0f2..439c1fe09d 100644
--- a/src/lib/dhcp/tests/option_int_unittest.cc
+++ b/src/lib/dhcp/tests/option_int_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -131,7 +131,7 @@ public:
// Data length is 2 bytes.
EXPECT_EQ(2, opt->len() - opt->getHeaderLen());
EXPECT_EQ(TEST_OPT_CODE, opt->getType());
- // The total length is 2 bytes for data and 2 or 4 bytes for aheader.
+ // The total length is 2 bytes for data and 2 or 4 bytes for a header.
if (u == Option::V4) {
EXPECT_EQ(4, out_buf_.getLength());
} else {
@@ -296,7 +296,7 @@ TEST_F(OptionIntTest, setValueUint8) {
EXPECT_EQ(Option::V6, opt->getUniverse());
EXPECT_EQ(D6O_PREFERENCE, opt->getType());
- // Check if the value has been overriden.
+ // Check if the value has been overridden.
EXPECT_EQ(111, opt->getValue());
}
@@ -310,7 +310,7 @@ TEST_F(OptionIntTest, setValueInt8) {
EXPECT_EQ(Option::V6, opt->getUniverse());
EXPECT_EQ(D6O_PREFERENCE, opt->getType());
- // Check if the value has been overriden.
+ // Check if the value has been overridden.
EXPECT_EQ(-111, opt->getValue());
}
@@ -325,7 +325,7 @@ TEST_F(OptionIntTest, setValueUint16) {
EXPECT_EQ(Option::V6, opt->getUniverse());
EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType());
- // Check if the value has been overriden.
+ // Check if the value has been overridden.
EXPECT_EQ(0x0102, opt->getValue());
}
@@ -339,7 +339,7 @@ TEST_F(OptionIntTest, setValueInt16) {
EXPECT_EQ(Option::V6, opt->getUniverse());
EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType());
- // Check if the value has been overriden.
+ // Check if the value has been overridden.
EXPECT_EQ(-20100, opt->getValue());
}
@@ -353,7 +353,7 @@ TEST_F(OptionIntTest, setValueUint32) {
EXPECT_EQ(Option::V6, opt->getUniverse());
EXPECT_EQ(D6O_CLT_TIME, opt->getType());
- // Check if the value has been overriden.
+ // Check if the value has been overridden.
EXPECT_EQ(0x01020304, opt->getValue());
}
@@ -367,7 +367,7 @@ TEST_F(OptionIntTest, setValueInt32) {
EXPECT_EQ(Option::V6, opt->getUniverse());
EXPECT_EQ(D6O_CLT_TIME, opt->getType());
- // Check if the value has been overriden.
+ // Check if the value has been overridden.
EXPECT_EQ(-125000, opt->getValue());
}
diff --git a/src/lib/dhcp/tests/option_space_unittest.cc b/src/lib/dhcp/tests/option_space_unittest.cc
index d31254398b..77b25aad5f 100644
--- a/src/lib/dhcp/tests/option_space_unittest.cc
+++ b/src/lib/dhcp/tests/option_space_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -35,7 +35,7 @@ TEST(OptionSpaceTest, constructor) {
}
// The purpose of this test is to verify that the vendor-space flag
-// can be overriden.
+// can be overridden.
TEST(OptionSpaceTest, setVendorSpace) {
OptionSpace space("isc", true);
EXPECT_EQ("isc", space.getName());
@@ -60,7 +60,7 @@ TEST(OptionSpaceTest, validateName) {
EXPECT_TRUE(OptionSpace::validateName("1234"));
EXPECT_TRUE(OptionSpace::validateName("UPPER_CASE_allowed"));
- // Negative test scenarions: empty strings, dots, spaces are not
+ // Negative test scenarios: empty strings, dots, spaces are not
// allowed
EXPECT_FALSE(OptionSpace::validateName(""));
EXPECT_FALSE(OptionSpace::validateName(" "));
diff --git a/src/lib/dhcp/tests/option_string_unittest.cc b/src/lib/dhcp/tests/option_string_unittest.cc
index 136f032376..f8fb02aac0 100644
--- a/src/lib/dhcp/tests/option_string_unittest.cc
+++ b/src/lib/dhcp/tests/option_string_unittest.cc
@@ -105,7 +105,7 @@ TEST_F(OptionStringTest, constructorFromBuffer) {
EXPECT_EQ(optv6_value, optv6->getValue());
}
-// This test verifies that the current option value can be overriden
+// This test verifies that the current option value can be overridden
// with new value, using setValue method.
TEST_F(OptionStringTest, setValue) {
// Create an instance of the option and set some initial value.
diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc
index c9fc49d762..0e282072ce 100644
--- a/src/lib/dhcp/tests/option_unittest.cc
+++ b/src/lib/dhcp/tests/option_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -131,7 +131,7 @@ TEST_F(OptionTest, v4_data2) {
data.push_back(67);
// Data contains extra garbage at beginning and at the end. It should be
- // ignored, as we pass interators to proper data. Only subset (limited by
+ // ignored, as we pass iterators to proper data. Only subset (limited by
// iterators) of the vector should be used.
// expData contains expected content (just valid data, without garbage).
scoped_ptr<Option> opt;
diff --git a/src/lib/dhcp/tests/option_vendor_unittest.cc b/src/lib/dhcp/tests/option_vendor_unittest.cc
index b792052b52..c7655e051d 100644
--- a/src/lib/dhcp/tests/option_vendor_unittest.cc
+++ b/src/lib/dhcp/tests/option_vendor_unittest.cc
@@ -44,7 +44,7 @@ public:
Length: 127
Enterprise ID: Cable Television Laboratories, Inc. (4491)
Suboption 1: Option Request
- Suboption 5: Modem capabilties */
+ Suboption 5: Modem capabilities */
string from_wireshark = "7d7f0000118b7a01010205750101010201030301010401"
"0105010106010107010f0801100901030a01010b01180c01010d0200400e020010"
"0f010110040000000211010014010015013f1601011701011801041901041a0104"
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index 45cb0d0529..d02c2cb28e 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -684,7 +684,7 @@ TEST_F(Pkt4Test, unpackMalformed) {
orig.push_back(0x53);
orig.push_back(0x63);
- orig.push_back(53); // Message Type
+ orig.push_back(53); // Message Type
orig.push_back(1); // length=1
orig.push_back(2); // type=2
@@ -727,7 +727,7 @@ TEST_F(Pkt4Test, unpackVendorMalformed) {
orig.push_back(0x53);
orig.push_back(0x63);
- orig.push_back(53); // Message Type
+ orig.push_back(53); // Message Type
orig.push_back(1); // length=1
orig.push_back(2); // type=2
@@ -943,7 +943,7 @@ TEST_F(Pkt4Test, clientClasses) {
TEST_F(Pkt4Test, getMAC) {
Pkt4 pkt(DHCPOFFER, 1234);
- // DHCPv4 packet by default doens't have MAC address specified.
+ // DHCPv4 packet by default doesn't have MAC address specified.
EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
@@ -1093,7 +1093,7 @@ TEST_F(Pkt4Test, toText) {
pkt.addOption(OptionPtr(new OptionUint32(Option::V4, 156, 123456)));
pkt.addOption(OptionPtr(new OptionString(Option::V4, 87, "lorem ipsum")));
- EXPECT_EQ("local_address=192.0.2.34:67, remote_adress=192.10.33.4:68, "
+ EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68, "
"msg_type=DHCPDISCOVER (1), transid=0x9ef,\n"
"options:\n"
" type=053, len=001: 1 (uint8)\n"
@@ -1109,7 +1109,7 @@ TEST_F(Pkt4Test, toText) {
pkt.delOption(87);
pkt.delOption(53);
- EXPECT_EQ("local_address=192.0.2.34:67, remote_adress=192.10.33.4:68, "
+ EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68, "
"msg_type=(missing), transid=0x9ef, "
"message contains no options",
pkt.toText());
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index 2ee4a2b034..f45b937c81 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -986,7 +986,7 @@ TEST_F(Pkt6Test, getAnyRelayOption) {
EXPECT_TRUE(opt->equals(relay3_opt1));
EXPECT_FALSE(opt == relay3_opt1);
// Test that option copy has replaced the original option within the
- // packet. We achive that by calling a variant of the method which
+ // packet. We achieve that by calling a variant of the method which
// retrieved non-copied option.
relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
ASSERT_TRUE(relay3_opt1);
@@ -1049,6 +1049,38 @@ TEST_F(Pkt6Test, getAnyRelayOption) {
EXPECT_FALSE(opt);
}
+// Tests whether Pkt6::toText() properly prints out all parameters, including
+// relay options: remote-id, interface-id.
+TEST_F(Pkt6Test, toText) {
+
+ // This packet contains doubly relayed solicit. The inner-most
+ // relay-forward contains interface-id and remote-id. We will
+ // check that these are printed correctly.
+ Pkt6Ptr msg(capture2());
+ EXPECT_NO_THROW(msg->unpack());
+
+ ASSERT_EQ(2, msg->relay_info_.size());
+
+ string expected =
+ "localAddr=[ff05::1:3]:547 remoteAddr=[fe80::1234]:547\n"
+ "msgtype=1(SOLICIT), transid=0x6b4fe2\n"
+ "type=00001, len=00014: 00:01:00:01:18:b0:33:41:00:00:21:5c:18:a9\n"
+ "type=00003(IA_NA), len=00012: iaid=1, t1=4294967295, t2=4294967295\n"
+ "type=00006, len=00006: 23(uint16) 242(uint16) 243(uint16)\n"
+ "type=00008, len=00002: 0 (uint16)\n"
+ "2 relay(s):\n"
+ "relay[0]: msg-type=12(RELAY_FORWARD), hop-count=1,\n"
+ "link-address=2001:888:db8:1::, peer-address=fe80::200:21ff:fe5c:18a9, 2 option(s)\n"
+ "type=00018, len=00028: 49:53:41:4d:31:34:34:7c:32:39:39:7c:69:70:76:36:7c:6e:74:3a:76:70:3a:31:3a:31:31:30\n"
+ "type=00037, len=00018: 6527 (uint32) 0001000118B033410000215C18A9 (binary)\n"
+ "relay[1]: msg-type=12(RELAY_FORWARD), hop-count=0,\n"
+ "link-address=::, peer-address=fe80::200:21ff:fe5c:18a9, 2 option(s)\n"
+ "type=00018, len=00021: 49:53:41:4d:31:34:34:20:65:74:68:20:31:2f:31:2f:30:35:2f:30:31\n"
+ "type=00037, len=00004: 3561 (uint32) (binary)\n";
+
+ EXPECT_EQ(expected, msg->toText());
+}
+
// Tests whether a packet can be assigned to a class and later
// checked if it belongs to a given class
TEST_F(Pkt6Test, clientClasses) {
@@ -1084,7 +1116,7 @@ TEST_F(Pkt6Test, clientClasses) {
TEST_F(Pkt6Test, getMAC) {
Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
- // DHCPv6 packet by default doens't have MAC address specified.
+ // DHCPv6 packet by default doesn't have MAC address specified.
EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
@@ -1578,7 +1610,7 @@ TEST_F(Pkt6Test, getClientId) {
EXPECT_TRUE(duid->getDuid() == duid_vec);
}
-// This test verfies that it is possible to obtain the packet
+// This test verifies that it is possible to obtain the packet
// identifiers (DUID, HW Address, transaction id) in the textual
// format.
TEST_F(Pkt6Test, makeLabel) {
@@ -1657,7 +1689,7 @@ TEST_F(Pkt6Test, getLabelEmptyClientId) {
// Create a packet.
Pkt6 pkt(DHCPV6_SOLICIT, 0x2312);
- // Add empty client idenitifier option.
+ // Add empty client identifier option.
pkt.addOption(OptionPtr(new Option(Option::V6, D6O_CLIENTID)));
EXPECT_EQ("duid=[no info], tid=0x2312", pkt.getLabel());
}
diff --git a/src/lib/dhcp/tests/pkt_captures4.cc b/src/lib/dhcp/tests/pkt_captures4.cc
index 9c33e65f3a..a7eaa13b3a 100644
--- a/src/lib/dhcp/tests/pkt_captures4.cc
+++ b/src/lib/dhcp/tests/pkt_captures4.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -25,7 +25,7 @@
/// 4. Right click on DHCPv6 -> Copy -> Bytes -> Hex Stream
/// 5. Paste it as: string hex_string="[paste here]";
/// 6. Coding guidelines line restrictions apply, so wrap your code as necessary
-/// 7. Make sure you decribe the capture appropriately
+/// 7. Make sure you describe the capture appropriately
/// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.)
/// 9. To easily copy packet description, click File... -> Extract packet
/// dissections -> as plain text file...
diff --git a/src/lib/dhcp/tests/pkt_captures6.cc b/src/lib/dhcp/tests/pkt_captures6.cc
index 5df9c63df8..f086f4b6ce 100644
--- a/src/lib/dhcp/tests/pkt_captures6.cc
+++ b/src/lib/dhcp/tests/pkt_captures6.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -25,7 +25,7 @@
/// 4. Right click on DHCPv6 -> Copy -> Bytes -> Hex Stream
/// 5. Paste it as: string hex_string="[paste here]";
/// 6. Coding guidelines line restrictions apply, so wrap your code as necessary
-/// 7. Make sure you decribe the capture appropriately
+/// 7. Make sure you describe the capture appropriately
/// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.)
/// 9. To easily copy packet description, click File... -> Extract packet
/// dissections -> as plain text file...
@@ -55,7 +55,7 @@ Pkt6Ptr PktCaptures::captureSimpleSolicit() {
1, // type 1 = SOLICIT
0xca, 0xfe, 0x01, // trans-id = 0xcafe01
0, 1, // option type 1 (client-id)
- 0, 10, // option lenth 10
+ 0, 10, // option length 10
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // DUID
0, 3, // option type 3 (IA_NA)
0, 12, // option length 12
diff --git a/src/lib/dhcp/tests/pkt_filter_test_stub.h b/src/lib/dhcp/tests/pkt_filter_test_stub.h
index ca4b0814eb..dedf6d3336 100644
--- a/src/lib/dhcp/tests/pkt_filter_test_stub.h
+++ b/src/lib/dhcp/tests/pkt_filter_test_stub.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -32,7 +32,7 @@ public:
///
/// This function checks if the direct response capability is supported,
/// i.e. if the server can respond to the client which doesn't have an
- /// address yet. For this dummy class, the true is alaways returned.
+ /// address yet. For this dummy class, the true is always returned.
///
/// @return always true.
virtual bool isDirectResponseSupported() const;
@@ -40,7 +40,7 @@ public:
/// @brief Simulate opening of the socket.
///
/// This function simulates opening a primary socket. Rather than open
- /// an actual socket, the stub peforms a read-only open of "/dev/null".
+ /// an actual socket, the stub performs a read-only open of "/dev/null".
/// The fd returned by this open saved as the socket's descriptor in the
/// SocketInfo structure. This way the filter consumes an actual
/// descriptor and retains it until its socket is closed.
diff --git a/src/lib/dhcp/tests/pkt_filter_test_utils.h b/src/lib/dhcp/tests/pkt_filter_test_utils.h
index b45da25f52..253df184d2 100644
--- a/src/lib/dhcp/tests/pkt_filter_test_utils.h
+++ b/src/lib/dhcp/tests/pkt_filter_test_utils.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -105,7 +105,7 @@ public:
///
/// This function checks if the direct response capability is supported,
/// i.e. if the server can respond to the client which doesn't have an
- /// address yet. For this dummy class, the true is alaways returned.
+ /// address yet. For this dummy class, the true is always returned.
///
/// @return always true.
virtual bool isDirectResponseSupported() const;
diff --git a/src/lib/dhcp/tests/pkt_filter_unittest.cc b/src/lib/dhcp/tests/pkt_filter_unittest.cc
index 824aa22a99..5d04e32f3b 100644
--- a/src/lib/dhcp/tests/pkt_filter_unittest.cc
+++ b/src/lib/dhcp/tests/pkt_filter_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -29,7 +29,7 @@ public:
}
};
-// This test verifies that the fallback socket is successfuly opened and
+// This test verifies that the fallback socket is successfully opened and
// bound using the protected function of the PktFilter class.
TEST_F(PktFilterBaseClassTest, openFallbackSocket) {
// Open socket using the function under test. Note that, we don't have to
diff --git a/src/lib/dhcp/tests/protocol_util_unittest.cc b/src/lib/dhcp/tests/protocol_util_unittest.cc
index a5a234a04a..d9e0cf9c9f 100644
--- a/src/lib/dhcp/tests/protocol_util_unittest.cc
+++ b/src/lib/dhcp/tests/protocol_util_unittest.cc
@@ -55,7 +55,7 @@ TEST(ProtocolUtilTest, checksum) {
// Let's set it to 2 and see whether it is included in the
// calculation.
chksum = ~calcChecksum(hdr, hdr_size, 2);
- // The checkum value should change.
+ // The checksum value should change.
EXPECT_EQ(0xb1e4, chksum);
}
@@ -344,7 +344,7 @@ TEST(ProtocolUtilTest, writeIpUdpHeader) {
// to the value that we expect to be read from a header since the value
// read from a header will be IPv4.
IOAddress src_addr("::1");
- // Read src address as an array of bytes because it is easely convertible
+ // Read src address as an array of bytes because it is easily convertible
// to IOAddress object.
uint8_t src_addr_data[4];
ASSERT_NO_THROW(
diff --git a/src/lib/dhcp_ddns/Makefile.am b/src/lib/dhcp_ddns/Makefile.am
index d510a8823a..99c84c4caa 100644
--- a/src/lib/dhcp_ddns/Makefile.am
+++ b/src/lib/dhcp_ddns/Makefile.am
@@ -34,7 +34,7 @@ libkea_dhcp_ddns_la_CXXFLAGS = $(AM_CXXFLAGS)
libkea_dhcp_ddns_la_CPPFLAGS = $(AM_CPPFLAGS)
libkea_dhcp_ddns_la_LDFLAGS = $(AM_LDFLAGS)
libkea_dhcp_ddns_la_LDFLAGS += $(CRYPTO_LDFLAGS)
-libkea_dhcp_ddns_la_LDFLAGS += -no-undefined -version-info 1:1:0
+libkea_dhcp_ddns_la_LDFLAGS += -no-undefined -version-info 1:2:0
libkea_dhcp_ddns_la_LIBADD =
libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
diff --git a/src/lib/dhcp_ddns/libdhcp_ddns.dox b/src/lib/dhcp_ddns/libdhcp_ddns.dox
index e6102f6bc4..c03e30c778 100644
--- a/src/lib/dhcp_ddns/libdhcp_ddns.dox
+++ b/src/lib/dhcp_ddns/libdhcp_ddns.dox
@@ -1,11 +1,11 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/**
-@page libdhcp_ddns libdhcp_ddns - DHCP_DDNS Request I/O Library
+@page libdhcp_ddns libkea-dhcp_ddns - DHCP_DDNS Request I/O Library
@section libdhcp_ddnsIntro Introduction
@@ -48,7 +48,7 @@ only transport supported is via UDP Sockets.
The UDP implementation is provided by isc::dhcp_ddns::NameChangeUDPSender
and isc::dhcp_ddns::NameChangeUDPListener. The implementation is strictly
-unidirectional: there is no explicit acknowledgement of receipt of a
+unidirectional: there is no explicit acknowledgment of receipt of a
request so, as it is UDP, no guarantee of delivery.
*/
diff --git a/src/lib/dhcp_ddns/ncr_io.cc b/src/lib/dhcp_ddns/ncr_io.cc
index a7c30c0e51..9068f13289 100644
--- a/src/lib/dhcp_ddns/ncr_io.cc
+++ b/src/lib/dhcp_ddns/ncr_io.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -189,8 +189,8 @@ NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
void
NameChangeSender::stopSending() {
- // Set it send indicator to false, no matter what. This allows us to at
- // least try to re-open via startSending(). Also, setting it false now,
+ // Set it send indicator to false, no matter what. This allows us to at
+ // least try to re-open via startSending(). Also, setting it false now,
// allows us to break sendNext() chain in invokeSendHandler.
setSending(false);
@@ -393,7 +393,7 @@ NameChangeSender::runReadyIO() {
}
// We shouldn't be here if IO isn't ready to execute.
- // By running poll we're gauranteed not to hang.
+ // By running poll we're guaranteed not to hang.
/// @todo Trac# 3325 requests that asiolink::IOService provide a
/// wrapper for poll().
io_service_->get_io_service().poll_one();
diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h
index e32bb326fa..dce72a5407 100644
--- a/src/lib/dhcp_ddns/ncr_io.h
+++ b/src/lib/dhcp_ddns/ncr_io.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -550,7 +550,7 @@ public:
///
/// @throw NcrSenderError if either sender is in send mode, if the number of
/// messages in the source sender's queue is larger than this sender's
- /// maxium queue size, or if this sender's queue is not empty.
+ /// maximum queue size, or if this sender's queue is not empty.
void assumeQueue(NameChangeSender& source_sender);
/// @brief Returns a file descriptor suitable for use with select
@@ -754,7 +754,7 @@ private:
/// @brief Boolean indicator which tracks sending status.
bool sending_;
- /// @brief A pointer to regisetered send completion handler.
+ /// @brief A pointer to registered send completion handler.
RequestSendHandler& send_handler_;
/// @brief Maximum number of entries permitted in the send queue.
diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h
index 065e1181bc..958f57509d 100644
--- a/src/lib/dhcp_ddns/ncr_msg.h
+++ b/src/lib/dhcp_ddns/ncr_msg.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -33,7 +33,7 @@ public:
isc::Exception(file, line, what) { };
};
-/// @brief Exception thrown when there is an error occured during computation
+/// @brief Exception thrown when there is an error occurred during computation
/// of the DHCID.
class DhcidRdataComputeError : public isc::Exception {
public:
@@ -180,14 +180,14 @@ public:
return (this->bytes_ != other.bytes_);
}
- /// @brief Compares two D2Dhcids lexcially
+ /// @brief Compares two D2Dhcids lexically
bool operator<(const D2Dhcid& other) const {
return (this->bytes_ < other.bytes_);
}
private:
- /// @brief Creates the DHCID using specified indetifier.
+ /// @brief Creates the DHCID using specified identifier.
///
/// This function creates the DHCID RDATA as specified in RFC4701,
/// section 3.5.
diff --git a/src/lib/dhcp_ddns/ncr_udp.cc b/src/lib/dhcp_ddns/ncr_udp.cc
index 048b5367f2..655f8b06e6 100644
--- a/src/lib/dhcp_ddns/ncr_udp.cc
+++ b/src/lib/dhcp_ddns/ncr_udp.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -114,7 +114,7 @@ NameChangeUDPListener::open(isc::asiolink::IOService& io_service) {
void
NameChangeUDPListener::doReceive() {
- // Call the socket's asychronous receiving, passing ourself in as callback.
+ // Call the socket's asynchronous receiving, passing ourself in as callback.
RawBufferPtr recv_buffer = recv_callback_->getBuffer();
socket_->asyncReceive(recv_buffer.get(), recv_callback_->getBufferSize(),
0, recv_callback_->getDataSource().get(),
@@ -174,7 +174,7 @@ NameChangeUDPListener::receiveCompletionHandler(const bool successful,
if (error_code.value() == boost::asio::error::operation_aborted) {
// A shutdown cancels all outstanding reads. For this reason,
// it can be an expected event, so log it as a debug message.
- LOG_DEBUG(dhcp_ddns_logger, DBGLVL_TRACE_BASIC,
+ LOG_DEBUG(dhcp_ddns_logger, isc::log::DBGLVL_TRACE_BASIC,
DHCP_DDNS_NCR_UDP_RECV_CANCELED);
result = STOPPED;
} else {
@@ -235,7 +235,7 @@ NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
}
- // Bind the low leve socket to our endpoint.
+ // Bind the low level socket to our endpoint.
asio_socket_->bind(endpoint.getASIOEndpoint());
} catch (boost::system::system_error& ex) {
isc_throw (NcrUDPError, ex.code().message());
@@ -294,7 +294,7 @@ NameChangeUDPSender::doSend(NameChangeRequestPtr& ncr) {
send_callback_->putData(static_cast<const uint8_t*>(ncr_buffer.getData()),
ncr_buffer.getLength());
- // Call the socket's asychronous send, passing our callback
+ // Call the socket's asynchronous send, passing our callback
socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
send_callback_->getDataSource().get(), *send_callback_);
diff --git a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
index eda0abcf21..e6ac57a739 100644
--- a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
+++ b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -720,7 +720,7 @@ TEST_F (NameChangeUDPTest, roundTripTest) {
}
// Tests error handling of a failure to mark the watch socket ready, when
-// sendRequestt() is called.
+// sendRequest() is called.
TEST(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequest) {
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
@@ -739,7 +739,7 @@ TEST(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequest) {
// Tamper with the watch socket by closing the select-fd.
close(sender.getSelectFd());
- // Send should fail as we interferred by closing the select-fd.
+ // Send should fail as we interfered by closing the select-fd.
ASSERT_THROW(sender.sendRequest(ncr), util::WatchSocketError);
// Verify we didn't invoke the handler.
@@ -776,7 +776,7 @@ TEST(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequest) {
close (sender.getSelectFd());
// Run one handler. This should execute the send completion handler
- // after sending the first message. Duing completion handling, we will
+ // after sending the first message. Doing completion handling, we will
// attempt to queue the second message which should fail.
ASSERT_NO_THROW(sender.runReadyIO());
@@ -823,7 +823,7 @@ TEST(NameChangeUDPSenderBasicTest, watchSocketBadRead) {
ASSERT_NE(util::WatchSocket::MARKER, buf);
// Run one handler. This should execute the send completion handler
- // after sending the message. Duing completion handling clearing the
+ // after sending the message. Doing completion handling clearing the
// watch socket should fail, which will close the socket, but not
// result in a throw.
ASSERT_NO_THROW(sender.runReadyIO());
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index 6c8f48bb0f..82af75510e 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -43,6 +43,11 @@ EXTRA_DIST += parsers/host_reservation_parser.h
EXTRA_DIST += parsers/host_reservations_list_parser.h
EXTRA_DIST += parsers/ifaces_config_parser.cc
EXTRA_DIST += parsers/ifaces_config_parser.h
+EXTRA_DIST += parsers/option_data_parser.h
+EXTRA_DIST += parsers/simple_parser4.cc
+EXTRA_DIST += parsers/simple_parser4.h
+EXTRA_DIST += parsers/simple_parser6.cc
+EXTRA_DIST += parsers/simple_parser6.h
# Devel guide diagrams
EXTRA_DIST += images/pgsql_host_data_source.svg
@@ -85,10 +90,11 @@ libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
libkea_dhcpsrv_la_SOURCES += alloc_engine_log.cc alloc_engine_log.h
libkea_dhcpsrv_la_SOURCES += base_host_data_source.h
libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
-libkea_dhcpsrv_la_SOURCES += cfg_4o6.h
+libkea_dhcpsrv_la_SOURCES += cfg_4o6.cc cfg_4o6.h
libkea_dhcpsrv_la_SOURCES += cfg_db_access.cc cfg_db_access.h
libkea_dhcpsrv_la_SOURCES += cfg_duid.cc cfg_duid.h
libkea_dhcpsrv_la_SOURCES += cfg_hosts.cc cfg_hosts.h
+libkea_dhcpsrv_la_SOURCES += cfg_hosts_util.cc cfg_hosts_util.h
libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
libkea_dhcpsrv_la_SOURCES += cfg_expiration.cc cfg_expiration.h
libkea_dhcpsrv_la_SOURCES += cfg_host_operations.cc cfg_host_operations.h
@@ -170,6 +176,12 @@ libkea_dhcpsrv_la_SOURCES += parsers/host_reservation_parser.h
libkea_dhcpsrv_la_SOURCES += parsers/host_reservations_list_parser.h
libkea_dhcpsrv_la_SOURCES += parsers/ifaces_config_parser.cc
libkea_dhcpsrv_la_SOURCES += parsers/ifaces_config_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/option_data_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/option_data_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.cc
+libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.h
+libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.cc
+libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.h
nodist_libkea_dhcpsrv_la_SOURCES = alloc_engine__messages.h
nodist_libkea_dhcpsrv_la_SOURCES += alloc_engine_messages.cc
@@ -194,7 +206,7 @@ libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
libkea_dhcpsrv_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
-libkea_dhcpsrv_la_LDFLAGS = -no-undefined -version-info 6:0:0
+libkea_dhcpsrv_la_LDFLAGS = -no-undefined -version-info 7:0:0
libkea_dhcpsrv_la_LDFLAGS += $(CRYPTO_LDFLAGS)
if HAVE_MYSQL
libkea_dhcpsrv_la_LDFLAGS += $(MYSQL_LIBS)
diff --git a/src/lib/dhcpsrv/addr_utilities.cc b/src/lib/dhcpsrv/addr_utilities.cc
index b55d8ec2d5..2513a1cee8 100644
--- a/src/lib/dhcpsrv/addr_utilities.cc
+++ b/src/lib/dhcpsrv/addr_utilities.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -32,6 +32,10 @@ const uint32_t bitMask4[] = { 0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff,
/// @brief mask used for first/last address calculation in a IPv6 prefix
const uint8_t bitMask6[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+/// @brief mask used for IPv6 prefix calculation
+const uint8_t revMask6[]= { 0xff, 0x7f, 0x3f, 0x1f, 0xf, 0x7, 0x3, 0x1 };
+
+
/// @brief calculates the first IPv6 address in a IPv6 prefix
///
/// Note: This is a private function. Do not use it directly.
@@ -270,6 +274,77 @@ addrsInRange(const isc::asiolink::IOAddress& min,
}
}
+int
+prefixLengthFromRange(const isc::asiolink::IOAddress& min,
+ const isc::asiolink::IOAddress& max) {
+ if (min.getFamily() != max.getFamily()) {
+ isc_throw(BadValue, "Both addresses have to be the same family");
+ }
+
+ if (max < min) {
+ isc_throw(BadValue, min.toText() << " must not be greater than "
+ << max.toText());
+ }
+
+ if (min.isV4()) {
+ // Get addresses as integers
+ uint32_t max_numeric = max.toUint32();
+ uint32_t min_numeric = min.toUint32();
+
+ // Get the exclusive or which must be one of the bit masks
+ uint32_t xor_numeric = max_numeric ^ min_numeric;
+ for (uint8_t prefix_len = 0; prefix_len <= 32; ++prefix_len) {
+ if (xor_numeric == bitMask4[prefix_len]) {
+ // Got it: the wanted value is also the index
+ return (static_cast<int>(prefix_len));
+ }
+ }
+
+ // If it was not found the range is not from a prefix / prefix_len
+ return (-1);
+ } else {
+ // Get addresses as 16 bytes
+ uint8_t min_packed[V6ADDRESS_LEN];
+ memcpy(min_packed, &min.toBytes()[0], 16);
+ uint8_t max_packed[V6ADDRESS_LEN];
+ memcpy(max_packed, &max.toBytes()[0], 16);
+
+ // Scan the exclusive or of addresses to find a difference
+ int candidate = 128;
+ bool zeroes = true;
+ for (uint8_t i = 0; i < 16; ++i) {
+ uint8_t xor_byte = min_packed[i] ^ max_packed[i];
+ if (zeroes) {
+ // Skipping zero bits searching for one bits
+ if (xor_byte == 0) {
+ continue;
+ }
+ // Found a one bit: note the fact
+ zeroes = false;
+ // Compare the exclusive or to masks
+ for (uint8_t j = 0; j < 8; ++j) {
+ if (xor_byte == revMask6[j]) {
+ // Got it the prefix length: note it
+ candidate = static_cast<int>((i * 8) + j);
+ }
+ }
+ if (candidate == 128) {
+ // Not found? The range is not from a prefix / prefix_len
+ return (-1);
+ }
+ } else {
+ // Checking that trailing bits are on bits
+ if (xor_byte == 0xff) {
+ continue;
+ }
+ // Not all ones is bad
+ return (-1);
+ }
+ }
+ return (candidate);
+ }
+}
+
uint64_t prefixesInRange(const uint8_t pool_len, const uint8_t delegated_len) {
if (delegated_len < pool_len) {
return (0);
diff --git a/src/lib/dhcpsrv/addr_utilities.h b/src/lib/dhcpsrv/addr_utilities.h
index f39c0a2c1f..77786e0749 100644
--- a/src/lib/dhcpsrv/addr_utilities.h
+++ b/src/lib/dhcpsrv/addr_utilities.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -63,6 +63,18 @@ isc::asiolink::IOAddress getNetmask4(uint8_t len);
uint64_t addrsInRange(const isc::asiolink::IOAddress& min,
const isc::asiolink::IOAddress& max);
+/// @brief Returns prefix length from the specified range (min - max).
+///
+/// This can be considered as log2(addrsInRange)
+///
+/// @throw BadValue if min and max do not define a prefix.
+///
+/// @param min the first address in range
+/// @param max the last address in range
+/// @return the prefix length or -1 if the range is not from a prefix
+int prefixLengthFromRange(const isc::asiolink::IOAddress& min,
+ const isc::asiolink::IOAddress& max);
+
/// @brief Returns number of available IPv6 prefixes in the specified prefix.
///
/// Note that if the answer is bigger than uint64_t can hold, it will return
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index bc37eebea3..121d7964cb 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -253,7 +253,7 @@ AllocEngine::AllocEngine(AllocType engine_type, uint64_t attempts,
// Choose the basic (normal address) lease type
Lease::Type basic_type = ipv6 ? Lease::TYPE_NA : Lease::TYPE_V4;
- // Initalize normal address allocators
+ // Initialize normal address allocators
switch (engine_type) {
case ALLOC_ITERATIVE:
allocators_[basic_type] = AllocatorPtr(new IterativeAllocator(basic_type));
@@ -268,7 +268,7 @@ AllocEngine::AllocEngine(AllocType engine_type, uint64_t attempts,
isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
}
- // If this is IPv6 allocation engine, initalize also temporary addrs
+ // If this is IPv6 allocation engine, initialize also temporary addrs
// and prefixes
if (ipv6) {
switch (engine_type) {
@@ -427,7 +427,7 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) {
// assign new leases
//
// We could implement those checks as nested ifs, but the performance
- // gain would be minimal and the code readibility loss would be substantial.
+ // gain would be minimal and the code readability loss would be substantial.
// Hence independent checks.
// Case 1: There are no leases and there's a reservation for this host.
@@ -710,6 +710,11 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
leases.push_back(lease);
return (leases);
+ } else if (ctx.callout_handle_ &&
+ (ctx.callout_handle_->getStatus() !=
+ CalloutHandle::NEXT_STEP_CONTINUE)) {
+ // Don't retry when the callout status is not continue.
+ break;
}
// Although the address was free just microseconds ago, it may have
@@ -1088,6 +1093,16 @@ AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
if (!ctx.fake_allocation_) {
// for REQUEST we do update the lease
LeaseMgrFactory::instance().updateLease6(expired);
+
+ // If the lease is in the current subnet we need to account
+ // for the re-assignment of The lease.
+ if (ctx.subnet_->inPool(ctx.currentIA().type_, expired->addr_)) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(1));
+ }
}
// We do nothing for SOLICIT. We'll just update database when
@@ -1382,6 +1397,15 @@ AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
if (old_data->expired()) {
reclaimExpiredLease(old_data, ctx.callout_handle_);
+ // If the lease is in the current subnet we need to account
+ // for the re-assignment of The lease.
+ if (ctx.subnet_->inPool(ctx.currentIA().type_, old_data->addr_)) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(1));
+ }
} else {
if (!lease->hasIdenticalFqdn(*old_data)) {
// We're not reclaiming the lease but since the FQDN has changed
@@ -1419,6 +1443,22 @@ AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases
lease->fqdn_rev_ = ctx.rev_dns_update_;
lease->hostname_ = ctx.hostname_;
if (!ctx.fake_allocation_) {
+
+ if (lease->state_ == Lease::STATE_EXPIRED_RECLAIMED) {
+ // Transition lease state to default (aka assigned)
+ lease->state_ = Lease::STATE_DEFAULT;
+
+ // If the lease is in the current subnet we need to account
+ // for the re-assignment of The lease.
+ if (ctx.subnet_->inPool(ctx.currentIA().type_, lease->addr_)) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(1));
+ }
+ }
+
bool fqdn_changed = ((lease->type_ != Lease::TYPE_PD) &&
!(lease->hasIdenticalFqdn(**lease_it)));
@@ -1840,7 +1880,7 @@ AllocEngine::reclaimExpiredLease(const Lease4Ptr& lease,
if (!skipped) {
// Generate removal name change request for D2, if required.
- // This will return immediatelly if the DNS wasn't updated
+ // This will return immediately if the DNS wasn't updated
// when the lease was created.
queueNCR(CHG_REMOVE, lease);
@@ -2651,13 +2691,14 @@ AllocEngine::renewLease4(const Lease4Ptr& lease,
// involves execution of hooks and DNS update.
if (ctx.old_lease_->expired()) {
reclaimExpiredLease(ctx.old_lease_, ctx.callout_handle_);
- lease->state_ = Lease::STATE_DEFAULT;
} else if (!lease->hasIdenticalFqdn(*ctx.old_lease_)) {
// The lease is not expired but the FQDN information has
// changed. So, we have to remove the previous DNS entry.
queueNCR(CHG_REMOVE, ctx.old_lease_);
}
+
+ lease->state_ = Lease::STATE_DEFAULT;
}
bool skip = false;
@@ -2705,6 +2746,13 @@ AllocEngine::renewLease4(const Lease4Ptr& lease,
if (!ctx.fake_allocation_ && !skip) {
// for REQUEST we do update the lease
LeaseMgrFactory::instance().updateLease4(lease);
+
+ // We need to account for the re-assignment of The lease.
+ if (ctx.old_lease_->expired() || ctx.old_lease_->state_ == Lease::STATE_EXPIRED_RECLAIMED) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(), "assigned-addresses"),
+ static_cast<int64_t>(1));
+ }
}
if (skip) {
// Rollback changes (really useful only for memfile)
@@ -2791,6 +2839,11 @@ AllocEngine::reuseExpiredLease4(Lease4Ptr& expired,
if (!ctx.fake_allocation_) {
// for REQUEST we do update the lease
LeaseMgrFactory::instance().updateLease4(expired);
+
+ // We need to account for the re-assignment of The lease.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(), "assigned-addresses"),
+ static_cast<int64_t>(1));
}
// We do nothing for SOLICIT. We'll just update database when
@@ -2841,6 +2894,11 @@ AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) {
new_lease = allocateOrReuseLease4(candidate, ctx);
if (new_lease) {
return (new_lease);
+ } else if (ctx.callout_handle_ &&
+ (ctx.callout_handle_->getStatus() !=
+ CalloutHandle::NEXT_STEP_CONTINUE)) {
+ // Don't retry when the callout status is not continue.
+ break;
}
}
}
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index c92ccfb8ff..3f3623118b 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -411,7 +411,7 @@ public:
bool isAllocated(const asiolink::IOAddress& prefix,
const uint8_t prefix_len = 128) const;
- /// @brief Conveniece function adding host identifier into
+ /// @brief Convenience function adding host identifier into
/// @ref host_identifiers_ list.
///
/// @param id_type Identifier type.
@@ -475,7 +475,7 @@ public:
/// @brief Allocates IPv6 leases for a given IA container
///
- /// This method uses the currently selected allocator to pick allocatable
+ /// This method uses the currently selected allocator to pick allocable
/// resources (i.e. addresses or prefixes) from specified subnet, creates
/// a lease (one or more, if needed) for that resources and then inserts
/// it into LeaseMgr (if this allocation is not fake, i.e. this is not a
@@ -590,12 +590,12 @@ public:
/// - executing "lease_expire6" hook,
/// - removing DNS record for a lease,
/// - reclaiming a lease in the database, i.e. setting its state to
- /// "expired-reclaimed" or removing it from the lease databse,
+ /// "expired-reclaimed" or removing it from the lease database,
/// - updating statistics of assigned and reclaimed leases
///
- /// Note: declined leases fall under the same expiration/reclaimation
+ /// Note: declined leases fall under the same expiration/reclamation
/// processing as normal leases. In principle, it would be more elegant
- /// to have a separate processing for declined leases reclaimation. However,
+ /// to have a separate processing for declined leases reclamation. However,
/// due to performance reasons we decided to use them together. Several
/// aspects were taken into consideration. First, normal leases are expected
/// to expire frequently, so in a typical deployment this method will have
@@ -617,7 +617,7 @@ public:
/// entry, stats dump, hooks).
///
/// @param max_leases Maximum number of leases to be reclaimed.
- /// @param timeout Maximum amount of time that the reclaimation routine
+ /// @param timeout Maximum amount of time that the reclamation routine
/// may be processing expired leases, expressed in milliseconds.
/// @param remove_lease A boolean value indicating if the lease should
/// be removed when it is reclaimed (if true) or it should be left in the
@@ -648,12 +648,12 @@ public:
/// - executing "lease_expire4" hook,
/// - removing DNS record for a lease,
/// - reclaiming a lease in the database, i.e. setting its state to
- /// "expired-reclaimed" or removing it from the lease databse,
+ /// "expired-reclaimed" or removing it from the lease database,
/// - updating statistics of assigned and reclaimed leases
///
- /// Note: declined leases fall under the same expiration/reclaimation
+ /// Note: declined leases fall under the same expiration/reclamation
/// processing as normal leases. In principle, it would be more elegant
- /// to have a separate processing for declined leases reclaimation. However,
+ /// to have a separate processing for declined leases reclamation. However,
/// due to performance reasons we decided to use them together. Several
/// aspects were taken into consideration. First, normal leases are expected
/// to expire frequently, so in a typical deployment this method will have
@@ -671,11 +671,11 @@ public:
/// declined leases. They are always removed.
///
/// Also, for declined leases @ref reclaimDeclinedLease4 is
- /// called. It conductsseveral declined specific operation (extra log
+ /// called. It conducts several declined specific operation (extra log
/// entry, stats dump, hooks).
///
/// @param max_leases Maximum number of leases to be reclaimed.
- /// @param timeout Maximum amount of time that the reclaimation routine
+ /// @param timeout Maximum amount of time that the reclamation routine
/// may be processing expired leases, expressed in milliseconds.
/// @param remove_lease A boolean value indicating if the lease should
/// be removed when it is reclaimed (if true) or it should be left in the
@@ -848,8 +848,8 @@ private:
ClientContext6& ctx,
uint8_t prefix_len);
- /// @brief Updates FQDN and Client's Last Tranmission Time for a collection
- /// of leases.
+ /// @brief Updates FQDN and Client's Last Transmission Time
+ /// for a collection of leases.
///
/// This method is executed when the server finds existing leases for a
/// client and updates some date for these leases if needed:
@@ -964,7 +964,7 @@ private:
/// @brief Marks lease as reclaimed in the database.
///
- /// This method is called internally by the leases reclaimation routines.
+ /// This method is called internally by the leases reclamation routines.
/// Depending on the value of the @c remove_lease parameter this method
/// will delete the reclaimed lease from the database or set its sate
/// to "expired-reclaimed". In the latter case it will also clear the
@@ -989,7 +989,7 @@ private:
/// @anchor reclaimDeclinedLease4
/// @brief Conducts steps necessary for reclaiming declined IPv4 lease.
///
- /// These are the additional steps required when recoving a declined lease:
+ /// These are the additional steps required when recovering a declined lease:
/// - bump decline recovered stat
/// - log lease recovery
/// - call lease4_recover hook
@@ -1002,7 +1002,7 @@ private:
/// @anchor reclaimDeclinedLease6
/// @brief Conducts steps necessary for reclaiming declined IPv6 lease.
///
- /// These are the additional steps required when recoving a declined lease:
+ /// These are the additional steps required when recovering a declined lease:
/// - bump decline recovered stat
/// - log lease recovery
/// - call lease6_recover hook
@@ -1092,7 +1092,7 @@ public:
/// received by the server.
IdentifierList host_identifiers_;
- /// @brief Conveniece function adding host identifier into
+ /// @brief Convenience function adding host identifier into
/// @ref host_identifiers_ list.
///
/// @param id_type Identifier type.
@@ -1348,7 +1348,7 @@ private:
/// or just picking an address for DISCOVER that is not really
/// allocated (true)
/// @return allocated lease (or NULL in the unlikely case of the lease just
- /// becomed unavailable)
+ /// become unavailable)
Lease4Ptr createLease4(const ClientContext4& ctx,
const isc::asiolink::IOAddress& addr);
@@ -1391,7 +1391,7 @@ private:
/// the new lease.
///
/// @param address Requested address for which the lease should be
- /// allocted.
+ /// allocated.
/// @param ctx Client context holding the data extracted from the
/// client's message.
///
diff --git a/src/lib/dhcpsrv/alloc_engine_log.h b/src/lib/dhcpsrv/alloc_engine_log.h
index b11b80f817..5c35a9eca3 100644
--- a/src/lib/dhcpsrv/alloc_engine_log.h
+++ b/src/lib/dhcpsrv/alloc_engine_log.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -19,23 +19,23 @@ namespace dhcp {
/// Defines the levels used to output debug messages from the @c AllocEngine.
/// @brief Traces normal operations
-const int ALLOC_ENGINE_DBG_TRACE = DBGLVL_TRACE_BASIC;
+const int ALLOC_ENGINE_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
/// @brief Records the results of various operations.
///
/// Messages logged at this level will typically contain summary of the
/// data retrieved.
-const int ALLOC_ENGINE_DBG_RESULTS = DBGLVL_TRACE_BASIC_DATA;
+const int ALLOC_ENGINE_DBG_RESULTS = isc::log::DBGLVL_TRACE_BASIC_DATA;
/// @brief Record detailed traces
///
/// Messages logged at this level will log detailed tracing information.
-const int ALLOC_ENGINE_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
+const int ALLOC_ENGINE_DBG_TRACE_DETAIL = isc::log::DBGLVL_TRACE_DETAIL;
/// @brief Records detailed results of various operations.
///
/// Messages logged at this level will contain detailed results.
-const int ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+const int ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA = isc::log::DBGLVL_TRACE_DETAIL_DATA;
//@}
diff --git a/src/lib/dhcpsrv/base_host_data_source.h b/src/lib/dhcpsrv/base_host_data_source.h
index 5decdb3c1e..e4d153ea6d 100644
--- a/src/lib/dhcpsrv/base_host_data_source.h
+++ b/src/lib/dhcpsrv/base_host_data_source.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -67,7 +67,7 @@ public:
/// in the future. Also, this list will grow over time. It is likely
/// that we'll implement other identifiers in the future, e.g. remote-id.
///
- /// Those value correspond direclty to dhcp_identifier_type in hosts
+ /// Those value correspond directly to dhcp_identifier_type in hosts
/// table in MySQL schema.
enum IdType {
ID_HWADDR = 0, ///< Hardware address
@@ -244,6 +244,46 @@ public:
/// @param host Pointer to the new @c Host object being added.
virtual void add(const HostPtr& host) = 0;
+ /// @brief Attempts to delete a host by (subnet-id, address)
+ ///
+ /// This method supports both v4 and v6.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) = 0;
+
+ /// @brief Attempts to delete a host by (subnet-id4, identifier, identifier-type)
+ ///
+ /// This method supports both v4 hosts only.
+ ///
+ /// @param subnet_id IPv4 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) = 0;
+
+ /// @brief Attempts to delete a host by (subnet-id6, identifier, identifier-type)
+ ///
+ /// This method supports both v6 hosts only.
+ ///
+ /// @param subnet_id IPv6 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) = 0;
+
/// @brief Return backend type
///
/// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
diff --git a/src/lib/dhcpsrv/cfg_4o6.cc b/src/lib/dhcpsrv/cfg_4o6.cc
new file mode 100644
index 0000000000..206298124f
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_4o6.cc
@@ -0,0 +1,49 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <dhcp/option.h>
+#include <dhcpsrv/cfg_4o6.h>
+#include <string>
+#include <string.h>
+#include <sstream>
+#include <vector>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+ElementPtr
+Cfg4o6::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set 4o6-interface
+ result->set("4o6-interface", Element::create(iface4o6_));
+ // Set 4o6-subnet
+ if (!subnet4o6_.first.isV6Zero() || (subnet4o6_.second != 128u)) {
+ std::ostringstream oss;
+ oss << subnet4o6_.first << "/"
+ << static_cast<unsigned>(subnet4o6_.second);
+ result->set("4o6-subnet", Element::create(oss.str()));
+ } else {
+ result->set("4o6-subnet", Element::create(std::string()));
+ }
+ // Set 4o6-interface-id
+ if (interface_id_) {
+ std::vector<uint8_t> bin = interface_id_->toBinary();
+ std::string iid;
+ iid.resize(bin.size());
+ if (!bin.empty()) {
+ std::memcpy(&iid[0], &bin[0], bin.size());
+ }
+ result->set("4o6-interface-id", Element::create(iid));
+ } else {
+ result->set("4o6-interface-id", Element::create(std::string()));
+ }
+ return (result);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/cfg_4o6.h b/src/lib/dhcpsrv/cfg_4o6.h
index 90bbf460ec..e231e775e6 100644
--- a/src/lib/dhcpsrv/cfg_4o6.h
+++ b/src/lib/dhcpsrv/cfg_4o6.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,8 +7,9 @@
#ifndef CFG_4OVER6_H
#define CFG_4OVER6_H
-#include <string>
#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
+#include <string>
namespace isc {
namespace dhcp {
@@ -17,7 +18,7 @@ namespace dhcp {
///
/// DHCP4o6 is completely optional. If it is not enabled, this structure
/// does not contain any information.
-struct Cfg4o6 {
+struct Cfg4o6 : public isc::data::CfgToElement {
/// the default constructor.
///
@@ -78,6 +79,11 @@ struct Cfg4o6 {
enabled_ = true;
}
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// Specifies if 4o6 is enabled on this subnet.
diff --git a/src/lib/dhcpsrv/cfg_db_access.cc b/src/lib/dhcpsrv/cfg_db_access.cc
index 4601dc93e6..e93631dc7b 100644
--- a/src/lib/dhcpsrv/cfg_db_access.cc
+++ b/src/lib/dhcpsrv/cfg_db_access.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,7 +9,13 @@
#include <dhcpsrv/host_data_source_factory.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
#include <sstream>
+#include <vector>
+
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -59,7 +65,63 @@ CfgDbAccess::getAccessString(const std::string& access_string) const {
return (s.str());
}
-
+ElementPtr
+CfgDbAccess::toElementDbAccessString(const std::string& dbaccess) {
+ ElementPtr result = Element::createMap();
+ // Code from DatabaseConnection::parse
+ if (dbaccess.empty()) {
+ return (result);
+ }
+ std::vector<std::string> tokens;
+ boost::split(tokens, dbaccess, boost::is_any_of(std::string("\t ")));
+ BOOST_FOREACH(std::string token, tokens) {
+ size_t pos = token.find("=");
+ if (pos != std::string::npos) {
+ std::string keyword = token.substr(0, pos);
+ std::string value = token.substr(pos + 1);
+ if ((keyword == "lfc-interval") ||
+ (keyword == "connect-timeout") ||
+ (keyword == "port")) {
+ // integer parameters
+ int64_t int_value;
+ try {
+ int_value = boost::lexical_cast<int64_t>(value);
+ result->set(keyword, Element::create(int_value));
+ } catch (...) {
+ isc_throw(ToElementError, "invalid DB access "
+ << "integer parameter: "
+ << keyword << "=" << value);
+ }
+ } else if ((keyword == "persist") ||
+ (keyword == "readonly")) {
+ if (value == "true") {
+ result->set(keyword, Element::create(true));
+ } else if (value == "false") {
+ result->set(keyword, Element::create(false));
+ } else {
+ isc_throw(ToElementError, "invalid DB access "
+ << "boolean parameter: "
+ << keyword << "=" << value);
+ }
+ } else if ((keyword == "type") ||
+ (keyword == "user") ||
+ (keyword == "password") ||
+ (keyword == "host") ||
+ (keyword == "name") ||
+ (keyword == "contact_points") ||
+ (keyword == "keyspace")) {
+ result->set(keyword, Element::create(value));
+ } else {
+ isc_throw(ToElementError, "unknown DB access parameter: "
+ << keyword << "=" << value);
+ }
+ } else {
+ isc_throw(ToElementError, "Cannot unparse " << token
+ << ", expected format is name=value");
+ }
+ }
+ return (result);
+}
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/cfg_db_access.h b/src/lib/dhcpsrv/cfg_db_access.h
index 77fda0d18a..2c87e4dafc 100644
--- a/src/lib/dhcpsrv/cfg_db_access.h
+++ b/src/lib/dhcpsrv/cfg_db_access.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
#ifndef CFG_DBACCESS_H
#define CFG_DBACCESS_H
+#include <cc/cfg_to_element.h>
#include <boost/shared_ptr.hpp>
#include <string>
@@ -63,11 +64,18 @@ public:
/// according to the configuration specified.
void createManagers() const;
-private:
+ /// @brief Unparse an access string
+ ///
+ /// @param dbaccess the database access string
+ /// @return a pointer to configuration
+ static
+ isc::data::ElementPtr toElementDbAccessString(const std::string& dbaccess);
+
+protected:
/// @brief Returns lease or host database access string.
///
- /// @param Access string without additional (appended) parameters.
+ /// @param access_string without additional (appended) parameters.
std::string getAccessString(const std::string& access_string) const;
/// @brief Parameters to be appended to the database access
@@ -88,6 +96,35 @@ typedef boost::shared_ptr<CfgDbAccess> CfgDbAccessPtr;
/// @brief A pointer to the const @c CfgDbAccess.
typedef boost::shared_ptr<const CfgDbAccess> ConstCfgDbAccessPtr;
+/// @brief utility class for unparsing
+struct CfgLeaseDbAccess : public CfgDbAccess, public isc::data::CfgToElement {
+ /// @brief Constructor
+ CfgLeaseDbAccess(const CfgDbAccess& super) : CfgDbAccess(super) { }
+
+ /// @brief Unparse
+ ///
+ /// @ref isc::data::CfgToElement::toElement
+ ///
+ /// @result a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const {
+ return (CfgDbAccess::toElementDbAccessString(lease_db_access_));
+ }
+};
+
+struct CfgHostDbAccess : public CfgDbAccess, public isc::data::CfgToElement {
+ /// @brief Constructor
+ CfgHostDbAccess(const CfgDbAccess& super) : CfgDbAccess(super) { }
+
+ /// @brief Unparse
+ ///
+ /// @ref isc::data::CfgToElement::toElement
+ ///
+ /// @result a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const {
+ return (CfgDbAccess::toElementDbAccessString(host_db_access_));
+ }
+};
+
}
}
diff --git a/src/lib/dhcpsrv/cfg_duid.cc b/src/lib/dhcpsrv/cfg_duid.cc
index d9c6e382ed..fffae1aa7e 100644
--- a/src/lib/dhcpsrv/cfg_duid.cc
+++ b/src/lib/dhcpsrv/cfg_duid.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,8 +9,11 @@
#include <util/encode/hex.h>
#include <util/strutil.h>
#include <iostream>
+#include <string>
+#include <string.h>
using namespace isc;
+using namespace isc::data;
using namespace isc::util::encode;
using namespace isc::util::str;
@@ -71,6 +74,39 @@ CfgDUID::create(const std::string& duid_file_path) const {
return (factory.get());
}
+ElementPtr
+CfgDUID::toElement() const {
+ ElementPtr result = Element::createMap();
+ // The type item is required
+ std::string duid_type = "LLT";
+ switch (type_) {
+ case DUID::DUID_LLT:
+ break;
+ case DUID::DUID_EN:
+ duid_type = "EN";
+ break;
+ case DUID::DUID_LL:
+ duid_type = "LL";
+ break;
+ default:
+ isc_throw(ToElementError, "invalid DUID type: " << getType());
+ break;
+ }
+ result->set("type", Element::create(duid_type));
+ // Set the identifier
+ result->set("identifier",
+ Element::create(util::encode::encodeHex(identifier_)));
+ // Set the hardware type
+ result->set("htype", Element::create(htype_));
+ // Set the time
+ result->set("time", Element::create(static_cast<long long>(time_)));
+ // Set the enterprise id
+ result->set("enterprise-id",
+ Element::create(static_cast<long long>(enterprise_id_)));
+ // Set the persistence flag
+ result->set("persist", Element::create(persist_));
+ return (result);
+}
}
}
diff --git a/src/lib/dhcpsrv/cfg_duid.h b/src/lib/dhcpsrv/cfg_duid.h
index b33057c5b6..dec70221f5 100644
--- a/src/lib/dhcpsrv/cfg_duid.h
+++ b/src/lib/dhcpsrv/cfg_duid.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#define CFG_DUID_H
#include <dhcp/duid.h>
+#include <cc/cfg_to_element.h>
#include <boost/shared_ptr.hpp>
#include <stdint.h>
#include <vector>
@@ -25,7 +26,7 @@ namespace dhcp {
/// generate. It also allows for overriding entire default DUID or parts of
/// it via configuration file. This class holds the DUID configuration
/// specified in the server configuration file.
-class CfgDUID {
+class CfgDUID : public isc::data::CfgToElement {
public:
/// @brief Constructor.
@@ -114,6 +115,11 @@ public:
/// @return Pointer to an instance of new DUID.
DuidPtr create(const std::string& duid_file_path) const;
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// @brief DUID type.
diff --git a/src/lib/dhcpsrv/cfg_expiration.cc b/src/lib/dhcpsrv/cfg_expiration.cc
index 6c5bce2a37..f4f1898cf9 100644
--- a/src/lib/dhcpsrv/cfg_expiration.cc
+++ b/src/lib/dhcpsrv/cfg_expiration.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,8 @@
#include <exceptions/exceptions.h>
#include <limits>
+using namespace isc::data;
+
namespace isc {
namespace dhcp {
@@ -94,7 +96,7 @@ CfgExpiration::rangeCheck(const int64_t value, const uint64_t max_value,
const std::string& config_parameter_name) const {
if (value < 0) {
isc_throw(OutOfRange, "value for configuration parameter '"
- << config_parameter_name << "' must not be negtive");
+ << config_parameter_name << "' must not be negative");
} else if (value > max_value) {
isc_throw(OutOfRange, "out range value '" << value << "' for configuration"
@@ -103,5 +105,35 @@ CfgExpiration::rangeCheck(const int64_t value, const uint64_t max_value,
}
}
+ElementPtr
+CfgExpiration::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set reclaim-timer-wait-time
+ result->set("reclaim-timer-wait-time",
+ Element::create(static_cast<long long>
+ (reclaim_timer_wait_time_)));
+ // Set flush-reclaimed-timer-wait-time
+ result->set("flush-reclaimed-timer-wait-time",
+ Element::create(static_cast<long long>
+ (flush_reclaimed_timer_wait_time_)));
+ // Set hold-reclaimed-time
+ result->set("hold-reclaimed-time",
+ Element::create(static_cast<long long>
+ (hold_reclaimed_time_)));
+ // Set max-reclaim-leases
+ result->set("max-reclaim-leases",
+ Element::create(static_cast<long long>
+ (max_reclaim_leases_)));
+ // Set max-reclaim-time
+ result->set("max-reclaim-time",
+ Element::create(static_cast<long long>
+ (max_reclaim_time_)));
+ // Set unwarned-reclaim-cycles
+ result->set("unwarned-reclaim-cycles",
+ Element::create(static_cast<long long>
+ (unwarned_reclaim_cycles_)));
+ return (result);
+}
+
}
}
diff --git a/src/lib/dhcpsrv/cfg_expiration.h b/src/lib/dhcpsrv/cfg_expiration.h
index 3ec8cbbaa2..de13d485c7 100644
--- a/src/lib/dhcpsrv/cfg_expiration.h
+++ b/src/lib/dhcpsrv/cfg_expiration.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#define CFG_EXPIRATION_H
#include <asiolink/interval_timer.h>
+#include <cc/cfg_to_element.h>
#include <dhcpsrv/timer_mgr.h>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
@@ -57,7 +58,7 @@ namespace dhcp {
/// The @c CfgExpiration class provides a collection of accessors and
/// modifiers to manage the data. Each accessor checks if the given value
/// is in range allowed for this value.
-class CfgExpiration {
+class CfgExpiration : public isc::data::CfgToElement {
public:
/// @name Default values.
@@ -98,7 +99,7 @@ public:
/// @brief Maximum value for max-reclaim-leases.
static const uint32_t LIMIT_MAX_RECLAIM_LEASES;
- /// @brief Defalt value for max-reclaim-time.
+ /// @brief Default value for max-reclaim-time.
static const uint16_t LIMIT_MAX_RECLAIM_TIME;
/// @brief Maximum value for unwarned-reclaim-cycles.
@@ -112,7 +113,7 @@ public:
/// @brief Name of the timer for reclaiming expired leases.
static const std::string RECLAIM_EXPIRED_TIMER_NAME;
- /// @brief Name of the timer for flushing relclaimed leases.
+ /// @brief Name of the timer for flushing reclaimed leases.
static const std::string FLUSH_RECLAIMED_TIMER_NAME;
//@}
@@ -223,6 +224,11 @@ public:
void (Instance::*delete_fun)(const uint32_t),
Instance* instance_ptr) const;
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// @brief Checks if the value being set by one of the modifiers is
@@ -307,7 +313,7 @@ CfgExpiration::setupTimers(void (Instance::*reclaim_fun)(const size_t,
timer_mgr_->setup(RECLAIM_EXPIRED_TIMER_NAME);
}
- // If the interval for the timer flusing expired-reclaimed leases
+ // If the interval for the timer flushing expired-reclaimed leases
// is set we will schedule the timer.
if (!flush_timer_disabled) {
// The interval is specified in milliseconds if we're in the test mode.
diff --git a/src/lib/dhcpsrv/cfg_host_operations.cc b/src/lib/dhcpsrv/cfg_host_operations.cc
index 6a19f1b454..662e4cd965 100644
--- a/src/lib/dhcpsrv/cfg_host_operations.cc
+++ b/src/lib/dhcpsrv/cfg_host_operations.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,9 @@
#include <exceptions/exceptions.h>
#include <dhcpsrv/cfg_host_operations.h>
#include <algorithm>
+#include <string>
+
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -23,6 +26,7 @@ CfgHostOperations::createConfig4() {
cfg->addIdentifierType("hw-address");
cfg->addIdentifierType("duid");
cfg->addIdentifierType("circuit-id");
+ cfg->addIdentifierType("client-id");
return (cfg);
}
@@ -52,5 +56,16 @@ CfgHostOperations::clearIdentifierTypes() {
identifier_types_.clear();
}
+ElementPtr
+CfgHostOperations::toElement() const {
+ ElementPtr result = Element::createList();
+ for (IdentifierTypes::const_iterator id = identifier_types_.begin();
+ id != identifier_types_.end(); ++id) {
+ const std::string& name = Host::getIdentifierName(*id);
+ result->add(Element::create(name));
+ }
+ return (result);
+}
+
}
}
diff --git a/src/lib/dhcpsrv/cfg_host_operations.h b/src/lib/dhcpsrv/cfg_host_operations.h
index eace0f8694..ae9f65831e 100644
--- a/src/lib/dhcpsrv/cfg_host_operations.h
+++ b/src/lib/dhcpsrv/cfg_host_operations.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
#ifndef CFG_HOST_OPERATIONS_H
#define CFG_HOST_OPERATIONS_H
+#include <cc/cfg_to_element.h>
#include <dhcpsrv/host.h>
#include <boost/shared_ptr.hpp>
#include <list>
@@ -39,7 +40,7 @@ ConstCfgHostOperationsPtr;
/// An administrator selects which identifiers the server should
/// use and in which order to search for host reservations to
/// optimize performance of the server.
-class CfgHostOperations {
+class CfgHostOperations : public isc::data::CfgToElement {
public:
/// @brief Type of the container holding ordered list of identifiers.
@@ -47,7 +48,7 @@ public:
/// @brief Constructor.
///
- /// The default confguration:
+ /// The default configuration:
/// - no identifiers selected for host reservations searches.
CfgHostOperations();
@@ -77,6 +78,11 @@ public:
/// @brief Removes existing identifier types.
void clearIdentifierTypes();
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// @brief Holds ordered collection of identifiers to be used by the
diff --git a/src/lib/dhcpsrv/cfg_hosts.cc b/src/lib/dhcpsrv/cfg_hosts.cc
index bc0c1167d2..b7f52d405e 100644
--- a/src/lib/dhcpsrv/cfg_hosts.cc
+++ b/src/lib/dhcpsrv/cfg_hosts.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,11 +6,17 @@
#include <config.h>
#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_hosts_util.h>
#include <dhcpsrv/hosts_log.h>
+#include <dhcpsrv/cfgmgr.h>
#include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
#include <ostream>
+#include <string>
+#include <vector>
using namespace isc::asiolink;
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -567,12 +573,13 @@ CfgHosts::add(const HostPtr& host) {
void
CfgHosts::add4(const HostPtr& host) {
- /// @todo This may need further sanity checks.
+
HWAddrPtr hwaddr = host->getHWAddress();
DuidPtr duid = host->getDuid();
// There should be at least one resource reserved: hostname, IPv4
// address, siaddr, sname, file or IPv6 address or prefix.
+ /// @todo: this check should be done in add(), not in add4()
if (host->getHostname().empty() &&
(host->getIPv4Reservation().isV4Zero()) &&
!host->hasIPv6Reservation() &&
@@ -627,7 +634,16 @@ CfgHosts::add4(const HostPtr& host) {
<< ": There's already a reservation for this address");
}
- /// @todo This may need further sanity checks.
+ // Check if the (identifier type, identifier) tuple is already used.
+ const std::vector<uint8_t>& id = host->getIdentifier();
+ if ((host->getIPv4SubnetID() > 0) && !id.empty()) {
+ if (get4(host->getIPv4SubnetID(), host->getIdentifierType(), &id[0],
+ id.size())) {
+ isc_throw(DuplicateHost, "failed to add duplicate IPv4 host using identifier: "
+ << Host::getIdentifierAsText(host->getIdentifierType(),
+ &id[0], id.size()));
+ }
+ }
// This is a new instance - add it.
hosts_.insert(host);
@@ -635,7 +651,12 @@ CfgHosts::add4(const HostPtr& host) {
void
CfgHosts::add6(const HostPtr& host) {
- /// @todo This may need further sanity checks.
+
+ if (host->getIPv6SubnetID() == 0) {
+ // This is IPv4-only host. No need to add it to v6 tables.
+ return;
+ }
+
HWAddrPtr hwaddr = host->getHWAddress();
DuidPtr duid = host->getDuid();
@@ -667,5 +688,81 @@ CfgHosts::add6(const HostPtr& host) {
}
}
+bool
+CfgHosts::del(const SubnetID& /*subnet_id*/, const asiolink::IOAddress& /*addr*/) {
+ /// @todo: Implement host removal
+ isc_throw(NotImplemented, "sorry, not implemented");
+ return (false);
+}
+
+bool
+CfgHosts::del4(const SubnetID& /*subnet_id*/,
+ const Host::IdentifierType& /*identifier_type*/,
+ const uint8_t* /*identifier_begin*/,
+ const size_t /*identifier_len*/) {
+ /// @todo: Implement host removal
+ isc_throw(NotImplemented, "sorry, not implemented");
+ return (false);
+}
+
+bool
+CfgHosts::del6(const SubnetID& /*subnet_id*/,
+ const Host::IdentifierType& /*identifier_type*/,
+ const uint8_t* /*identifier_begin*/,
+ const size_t /*identifier_len*/) {
+ /// @todo: Implement host removal
+ isc_throw(NotImplemented, "sorry, not implemented");
+ return (false);
+}
+
+ElementPtr
+CfgHosts::toElement() const {
+ uint16_t family = CfgMgr::instance().getFamily();
+ if (family == AF_INET) {
+ return (toElement4());
+ } else if (family == AF_INET6) {
+ return (toElement6());
+ } else {
+ isc_throw(ToElementError, "CfgHosts::toElement: unknown "
+ "address family: " << family);
+ }
+}
+
+ElementPtr
+CfgHosts::toElement4() const {
+ CfgHostsList result;
+ // Iterate using arbitrary the index 0
+ const HostContainerIndex0& idx = hosts_.get<0>();
+ for (HostContainerIndex0::const_iterator host = idx.begin();
+ host != idx.end(); ++host) {
+
+ // Convert host to element representation
+ ElementPtr map = (*host)->toElement4();
+
+ // Push it on the list
+ SubnetID subnet_id = (*host)->getIPv4SubnetID();
+ result.add(subnet_id, map);
+ }
+ return (result.externalize());
+}
+
+ElementPtr
+CfgHosts::toElement6() const {
+ CfgHostsList result;
+ // Iterate using arbitrary the index 0
+ const HostContainerIndex0& idx = hosts_.get<0>();
+ for (HostContainerIndex0::const_iterator host = idx.begin();
+ host != idx.end(); ++host) {
+
+ // Convert host to Element representation
+ ElementPtr map = (*host)->toElement6();
+
+ // Push it on the list
+ SubnetID subnet_id = (*host)->getIPv6SubnetID();
+ result.add(subnet_id, map);
+ }
+ return (result.externalize());
+}
+
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cfg_hosts.h b/src/lib/dhcpsrv/cfg_hosts.h
index 196ac20be0..0717e9a174 100644
--- a/src/lib/dhcpsrv/cfg_hosts.h
+++ b/src/lib/dhcpsrv/cfg_hosts.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#define CFG_HOSTS_H
#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
#include <dhcpsrv/base_host_data_source.h>
@@ -35,7 +36,8 @@ namespace dhcp {
/// when the new configuration is applied for the server. The reservations
/// are retrieved by the @c HostMgr class when the server is allocating or
/// renewing an address or prefix for the particular client.
-class CfgHosts : public BaseHostDataSource, public WritableHostDataSource {
+class CfgHosts : public BaseHostDataSource, public WritableHostDataSource,
+ public isc::data::CfgToElement {
public:
/// @brief Destructor.
@@ -77,7 +79,7 @@ public:
/// because a particular client may have reservations in multiple subnets.
///
/// @param identifier_type One of the supported identifier types.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -93,7 +95,7 @@ public:
/// because a particular client may have reservations in multiple subnets.
///
/// @param identifier_type One of the supported identifier types.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -181,7 +183,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -195,7 +197,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -249,7 +251,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -263,7 +265,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -320,6 +322,45 @@ public:
/// has already been added to the IPv4 or IPv6 subnet.
virtual void add(const HostPtr& host);
+ /// @brief Attempts to delete a host by address.
+ ///
+ /// This method supports both v4 and v6.
+ /// @todo: Not implemented.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr);
+
+ /// @brief Attempts to delete a host by (subnet4-id, identifier, identifier-type)
+ ///
+ /// This method supports v4 only.
+ /// @todo: Not implemented.
+ ///
+ /// @param subnet_id IPv4 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false otherwise.
+ virtual bool del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Attempts to delete a host by (subnet6-id, identifier, identifier-type)
+ ///
+ /// This method supports v6 only.
+ /// @todo: Not implemented.
+ ///
+ /// @param subnet_id IPv6 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false otherwise.
+ virtual bool del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
/// @brief Return backend type
///
/// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
@@ -329,6 +370,24 @@ public:
return (std::string("configuration file"));
}
+ /// @brief Unparse a configuration object
+ ///
+ /// host reservation lists are not autonomous so they are
+ /// not returned directly but with the subnet where they are
+ /// declared as:
+ /// @code
+ /// [
+ /// { "id": 123, "reservations": [ <resv1>, <resv2> ] },
+ /// { "id": 456, "reservations": [ <resv3 ] },
+ /// ...
+ /// ]
+ /// @endcode
+ ///
+ /// @ref isc::dhcp::CfgHostsList can be used to handle this
+ ///
+ /// @return a pointer to unparsed configuration
+ isc::data::ElementPtr toElement() const;
+
private:
/// @brief Returns @c Host objects for the specific identifier and type.
@@ -340,7 +399,7 @@ private:
/// @param identifier_type The type of the supplied identifier.
/// @param identifier Pointer to a first byte of the identifier.
/// @param identifier_len Length of the identifier.
- /// @param [out] storage Container to which the retreived objects are
+ /// @param [out] storage Container to which the retrieved objects are
/// appended.
/// @tparam One of the @c ConstHostCollection of @c HostCollection.
template<typename Storage>
@@ -421,7 +480,7 @@ private:
/// @param subnet_id IPv4 or IPv6 subnet identifier.
/// @param subnet6 A boolean flag which indicates if the subnet identifier
/// points to a IPv4 (if false) or IPv6 subnet (if true).
- /// @param identifier_type Indentifier type.
+ /// @param identifier_type Identifier type.
/// @param identifier Pointer to a first byte of the buffer holding an
/// identifier.
/// @param identifier_len Identifier length.
@@ -450,15 +509,18 @@ private:
/// @c Host object.
template<typename ReturnType, typename Storage>
ReturnType getHostInternal6(const SubnetID& subnet_id,
- const asiolink::IOAddress& adddress) const;
+ const asiolink::IOAddress& address) const;
template<typename ReturnType>
ReturnType getHostInternal6(const asiolink::IOAddress& prefix,
const uint8_t prefix_len) const;
- /// @brief Adds a new host to the v4 collection.
+ /// @brief Adds a new host to the collection.
///
- /// This is an internal method called by public @ref add.
+ /// This is an internal method called by public @ref add. Contrary to its
+ /// name, this is useful for both IPv4 and IPv6 hosts, as this adds the
+ /// host to hosts_ storage that is shared by both families. Notes that
+ /// for IPv6 host additional steps may be required (see @ref add6).
///
/// @param host Pointer to the new @c Host object being added.
///
@@ -466,9 +528,11 @@ private:
/// has already been added to the IPv4 subnet.
virtual void add4(const HostPtr& host);
- /// @brief Adds a new host to the v6 collection.
+ /// @brief Adds IPv6-specific reservation to hosts collection.
///
- /// This is an internal method called by public @ref add.
+ /// This is an internal method called by public @ref add. This method adds
+ /// IPv6 reservations (IPv6 addresses or prefixes reserved) to the hosts6_
+ /// storage. Note the host has been added to the hosts_ already (in @ref add4).
///
/// @param host Pointer to the new @c Host object being added.
///
@@ -491,6 +555,16 @@ private:
/// - IPv6 address
/// - IPv6 prefix
HostContainer6 hosts6_;
+
+ /// @brief Unparse a configuration object (DHCPv4 reservations)
+ ///
+ /// @return a pointer to unparsed configuration
+ isc::data::ElementPtr toElement4() const;
+
+ /// @brief Unparse a configuration object (DHCPv6 reservations)
+ ///
+ /// @return a pointer to unparsed configuration
+ isc::data::ElementPtr toElement6() const;
};
/// @name Pointers to the @c CfgHosts objects.
diff --git a/src/lib/dhcpsrv/cfg_hosts_util.cc b/src/lib/dhcpsrv/cfg_hosts_util.cc
new file mode 100644
index 0000000000..5798bf51d2
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_hosts_util.cc
@@ -0,0 +1,94 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <cc/data.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/cfg_hosts_util.h>
+#include <exceptions/exceptions.h>
+#include <boost/pointer_cast.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+void CfgHostsList::internalize(ConstElementPtr list) {
+ if (!list) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "argument is NULL");
+ }
+ if (list->getType() != Element::list) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "argument is not a list Element");
+ }
+ for (size_t i = 0; i < list->size(); ++i) {
+ ConstElementPtr item = list->get(i);
+ if (!item) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "null pointer from the list at " << i);
+ }
+ if (item->getType() != Element::map) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "not a map from the list at " << i);
+ }
+ if (item->size() != 2) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "bad map size from the list at " << i);
+ }
+ ConstElementPtr id = item->get("id");
+ if (!id) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "no id from a map at " << i);
+ }
+ if (id->getType() != Element::integer) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "not integer id from a map at " <<i);
+ }
+ SubnetID subnet_id = static_cast<SubnetID>(id->intValue());
+ ConstElementPtr resvs = item->get("reservations");
+ if (!resvs) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "no reservations for subnet ID " << subnet_id);
+ }
+ map_.insert(std::make_pair(subnet_id,
+ boost::const_pointer_cast<Element>(resvs)));
+ }
+}
+
+ElementPtr CfgHostsList::externalize() const {
+ ElementPtr result = Element::createList();
+ for (CfgHostsMap::const_iterator item = map_.begin();
+ item != map_.end(); ++item) {
+ ElementPtr pair = Element::createMap();
+ pair->set("id", Element::create(static_cast<int64_t>(item->first)));
+ pair->set("reservations", item->second);
+ result->add(pair);
+ }
+ return (result);
+}
+
+void CfgHostsList::add(SubnetID id, isc::data::ElementPtr resv) {
+ CfgHostsMap::iterator item = map_.find(id);
+ if (item != map_.end()) {
+ item->second->add(resv);
+ } else {
+ ElementPtr resvs = Element::createList();
+ resvs->add(resv);
+ map_.insert(std::make_pair(id, resvs));
+ }
+}
+
+ConstElementPtr CfgHostsList::get(SubnetID id) const {
+ CfgHostsMap::const_iterator item = map_.find(id);
+ if (item != map_.end()) {
+ return (item->second);
+ } else {
+ return (Element::createList());
+ }
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/cfg_hosts_util.h b/src/lib/dhcpsrv/cfg_hosts_util.h
new file mode 100644
index 0000000000..67803b9990
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_hosts_util.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef CFG_HOSTS_UTIL_H
+#define CFG_HOSTS_UTIL_H
+
+#include <cc/data.h>
+#include <dhcpsrv/subnet_id.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Utility class to represent host reservation configurations
+/// internally as a map keyed by subnet IDs, externally as a list Element.
+class CfgHostsList {
+public:
+
+ /// The type of the internal map
+ typedef std::map<SubnetID, isc::data::ElementPtr> CfgHostsMap;
+
+ /// @brief Internalize a list Element
+ ///
+ /// This method gets a list Element and builds the internal map from it.
+ ///
+ /// @param list the list Element
+ void internalize(isc::data::ConstElementPtr list);
+
+ /// @brief Externalize the map to a list Element
+ ///
+ /// @return a list Element representing all host reservations
+ isc::data::ElementPtr externalize() const;
+
+ /// @brief Add a host reservation to the map
+ void add(SubnetID id, isc::data::ElementPtr resv);
+
+ /// @brief Return the host reservations for a subnet ID
+ ///
+ /// @param id the subnet ID
+ /// @return a list Element with host reservations
+ isc::data::ConstElementPtr get(SubnetID id) const;
+
+private:
+ /// @brief The internal map
+ CfgHostsMap map_;
+};
+
+}
+}
+
+#endif // CFG_HOSTS_UTIL_H
diff --git a/src/lib/dhcpsrv/cfg_iface.cc b/src/lib/dhcpsrv/cfg_iface.cc
index 6ff26f10f6..c7482b1cc4 100644
--- a/src/lib/dhcpsrv/cfg_iface.cc
+++ b/src/lib/dhcpsrv/cfg_iface.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -14,6 +14,7 @@
#include <algorithm>
using namespace isc::asiolink;
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -21,7 +22,7 @@ namespace dhcp {
const char* CfgIface::ALL_IFACES_KEYWORD = "*";
CfgIface::CfgIface()
- : wildcard_used_(false), socket_type_(SOCKET_RAW) {
+ : wildcard_used_(false), socket_type_(SOCKET_RAW), re_detect_(false) {
}
void
@@ -398,5 +399,37 @@ CfgIface::useSocketType(const uint16_t family,
useSocketType(family, textToSocketType(socket_type_name));
}
+ElementPtr
+CfgIface::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ // Set interfaces
+ ElementPtr ifaces = Element::createList();
+ if (wildcard_used_) {
+ ifaces->add(Element::create(std::string(ALL_IFACES_KEYWORD)));
+ }
+ for (IfaceSet::const_iterator iface = iface_set_.cbegin();
+ iface != iface_set_.cend(); ++iface) {
+ ifaces->add(Element::create(*iface));
+ }
+ for (ExplicitAddressMap::const_iterator address = address_map_.cbegin();
+ address != address_map_.cend(); ++address) {
+ std::string spec = address->first + "/" + address->second.toText();
+ ifaces->add(Element::create(spec));
+ }
+ result->set("interfaces", ifaces);
+
+ // Set dhcp-socket-type (no default because it is DHCPv4 specific)
+ // @todo emit raw if and only if DHCPv4
+ if (socket_type_ != SOCKET_RAW) {
+ result->set("dhcp-socket-type", Element::create(std::string("udp")));
+ }
+
+ // Set re-detect
+ result->set("re-detect", Element::create(re_detect_));
+
+ return (result);
+}
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/cfg_iface.h b/src/lib/dhcpsrv/cfg_iface.h
index 40f3b53815..eea5fdb5a5 100644
--- a/src/lib/dhcpsrv/cfg_iface.h
+++ b/src/lib/dhcpsrv/cfg_iface.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
#include <asiolink/io_address.h>
#include <dhcp/iface_mgr.h>
+#include <cc/cfg_to_element.h>
#include <boost/shared_ptr.hpp>
#include <map>
#include <set>
@@ -114,7 +115,7 @@ public:
/// socket and bind to link local address as well as open a socket bound to
/// the specified unicast address.
///
-/// The DHCPv4 configuration doesn't accept the simulatenous use of the
+/// The DHCPv4 configuration doesn't accept the simultaneous use of the
/// "interface-name" and the "interface-name/address" tuple for the
/// given interface. When the "interface-name" is specified it implies
/// that the sockets will be opened on for all addresses configured on
@@ -125,7 +126,7 @@ public:
/// to which it is bound. It is allowed to select multiple addresses on the
/// particular interface explicitly, e.g. "eth0/192.168.8.1",
/// "eth0/192.168.8.2".
-class CfgIface {
+class CfgIface : public isc::data::CfgToElement {
public:
/// @brief Socket type used by the DHCPv4 server.
@@ -177,11 +178,11 @@ public:
/// @brief Select interface to be used to receive DHCP traffic.
///
- /// @ref CfgIface for a detail explaination of the interface name argument.
+ /// @ref CfgIface for a detail explanation of the interface name argument.
///
/// @param family Address family (AF_INET or AF_INET6).
/// @param iface_name Explicit interface name, a wildcard name (*) of
- /// the interface(s) or the pair of iterface/unicast-address to be used
+ /// the interface(s) or the pair of interface/unicast-address to be used
/// to receive DHCP traffic.
///
/// @throw InvalidIfaceName If the interface name is incorrect, e.g. empty.
@@ -251,6 +252,18 @@ public:
return (!equals(other));
}
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Set the re-detect flag
+ ///
+ /// @param re_detect the new value of the flag
+ void setReDetect(bool re_detect) {
+ re_detect_ = re_detect;
+ }
+
private:
/// @brief Checks if multiple IPv4 addresses has been activated on any
@@ -258,7 +271,7 @@ private:
///
/// This method is useful to check if the current configuration uses
/// multiple IPv4 addresses on any interface. This is important when
- /// using raw sockets to recieve messages from the clients because
+ /// using raw sockets to receive messages from the clients because
/// each packet may be received multiple times when it is sent from
/// a directly connected client. If this is the case, a warning must
/// be logged.
@@ -318,12 +331,15 @@ private:
/// for which the sockets should be opened.
ExplicitAddressMap address_map_;
- /// @brief A booolean value which indicates that the wildcard interface name
+ /// @brief A boolean value which indicates that the wildcard interface name
/// has been specified (*).
bool wildcard_used_;
/// @brief A type of the sockets used by the DHCP server.
SocketType socket_type_;
+
+ /// @brief A boolean value which reflects current re-detect setting
+ bool re_detect_;
};
/// @brief A pointer to the @c CfgIface .
diff --git a/src/lib/dhcpsrv/cfg_mac_source.cc b/src/lib/dhcpsrv/cfg_mac_source.cc
index 4307d407c1..7a0db260b3 100644
--- a/src/lib/dhcpsrv/cfg_mac_source.cc
+++ b/src/lib/dhcpsrv/cfg_mac_source.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,32 @@
#include <exceptions/exceptions.h>
#include <dhcp/hwaddr.h>
+using namespace isc::data;
+
+namespace {
+
+using namespace isc::dhcp;
+
+struct {
+ const char * name;
+ uint32_t type;
+} sources[] = {
+ { "any", HWAddr::HWADDR_SOURCE_ANY },
+ { "raw", HWAddr::HWADDR_SOURCE_RAW },
+ { "duid", HWAddr::HWADDR_SOURCE_DUID },
+ { "ipv6-link-local", HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL },
+ { "client-link-addr-option", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION },
+ { "rfc6939", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION },
+ { "remote-id", HWAddr::HWADDR_SOURCE_REMOTE_ID },
+ { "rfc4649", HWAddr::HWADDR_SOURCE_REMOTE_ID },
+ { "subscriber-id", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID },
+ { "rfc4580", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID },
+ { "docsis-cmts", HWAddr::HWADDR_SOURCE_DOCSIS_CMTS },
+ { "docsis-modem", HWAddr::HWADDR_SOURCE_DOCSIS_MODEM }
+};
+
+};
+
namespace isc {
namespace dhcp {
@@ -19,26 +45,7 @@ CfgMACSource::CfgMACSource() {
}
uint32_t CfgMACSource::MACSourceFromText(const std::string& name) {
-
- struct {
- const char * name;
- uint32_t type;
- } sources[] = {
- { "any", HWAddr::HWADDR_SOURCE_ANY },
- { "raw", HWAddr::HWADDR_SOURCE_RAW },
- { "duid", HWAddr::HWADDR_SOURCE_DUID },
- { "ipv6-link-local", HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL },
- { "client-link-addr-option", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION },
- { "rfc6939", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION },
- { "remote-id", HWAddr::HWADDR_SOURCE_REMOTE_ID },
- { "rfc4649", HWAddr::HWADDR_SOURCE_REMOTE_ID },
- { "subscriber-id", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID },
- { "rfc4580", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID },
- { "docsis-cmts", HWAddr::HWADDR_SOURCE_DOCSIS_CMTS },
- { "docsis-modem", HWAddr::HWADDR_SOURCE_DOCSIS_MODEM }
- };
-
- for (int i=0; i < sizeof(sources)/sizeof(sources[0]); ++i) {
+ for (unsigned i = 0; i < sizeof(sources)/sizeof(sources[0]); ++i) {
if (name.compare(sources[i].name) == 0) {
return (sources[i].type);
}
@@ -47,6 +54,36 @@ uint32_t CfgMACSource::MACSourceFromText(const std::string& name) {
isc_throw(BadValue, "Can't convert '" << name << "' to any known MAC source.");
}
+void CfgMACSource::add(uint32_t source) {
+ for (CfgMACSources::const_iterator it = mac_sources_.begin();
+ it != mac_sources_.end(); ++it) {
+ if (*it == source) {
+ isc_throw(InvalidParameter, "mac-source parameter " << source
+ << "' specified twice.");
+ }
+ }
+ mac_sources_.push_back(source);
+}
+
+ElementPtr CfgMACSource::toElement() const {
+ ElementPtr result = Element::createList();
+ for (CfgMACSources::const_iterator source = mac_sources_.cbegin();
+ source != mac_sources_.cend(); ++source) {
+ std::string name;
+ for (unsigned i = 0; i < sizeof(sources)/sizeof(sources[0]); ++i) {
+ if (sources[i].type == *source) {
+ name = sources[i].name;
+ break;
+ }
+ }
+ if (name.empty()) {
+ isc_throw(ToElementError, "invalid MAC source: " << *source);
+ }
+ result->add(Element::create(name));
+ }
+ // @todo check if the list is empty (including a new unit test)
+ return (result);
+}
};
};
diff --git a/src/lib/dhcpsrv/cfg_mac_source.h b/src/lib/dhcpsrv/cfg_mac_source.h
index cddfceedfb..87c673dd3d 100644
--- a/src/lib/dhcpsrv/cfg_mac_source.h
+++ b/src/lib/dhcpsrv/cfg_mac_source.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
#ifndef CFG_MAC_SOURCE_H
#define CFG_MAC_SOURCE_H
+#include <cc/cfg_to_element.h>
#include <stdint.h>
#include <vector>
#include <string>
@@ -21,7 +22,7 @@ typedef std::vector<uint32_t> CfgMACSources;
///
/// It's a simple wrapper around a vector of uint32_t, with each entry
/// holding one MAC source.
-class CfgMACSource {
+class CfgMACSource : public isc::data::CfgToElement {
public:
/// @brief Default constructor.
@@ -47,15 +48,13 @@ class CfgMACSource {
static uint32_t MACSourceFromText(const std::string& name);
- /// @brief Adds additional MAC/hardware address aquisition.
+ /// @brief Adds additional MAC/hardware address acquisition.
///
/// @param source MAC source (see constants in Pkt::HWADDR_SOURCE_*)
///
/// Specified source is being added to the mac_sources_ array.
- /// @todo implement add(string) version of this method.
- void add(uint32_t source) {
- mac_sources_.push_back(source);
- }
+ /// @throw InvalidParameter if such a source is already defined.
+ void add(uint32_t source);
/// @brief Provides access to the configure MAC/Hardware address sources.
///
@@ -70,6 +69,11 @@ class CfgMACSource {
mac_sources_.clear();
}
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
protected:
/// @brief Actual MAC sources storage
CfgMACSources mac_sources_;
diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc
index 8947dd5335..5d6c153918 100644
--- a/src/lib/dhcpsrv/cfg_option.cc
+++ b/src/lib/dhcpsrv/cfg_option.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,7 +8,12 @@
#include <dhcp/option_space.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcp/dhcp6.h>
+#include <util/encode/hex.h>
#include <string>
+#include <sstream>
+#include <vector>
+
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -183,5 +188,93 @@ CfgOption::getAll(const uint32_t vendor_id) const {
return (vendor_options_.getItems(vendor_id));
}
+ElementPtr
+CfgOption::toElement() const {
+ // option-data value is a list of maps
+ ElementPtr result = Element::createList();
+ // Iterate first on options using space names
+ const std::list<std::string>& names = options_.getOptionSpaceNames();
+ for (std::list<std::string>::const_iterator name = names.begin();
+ name != names.end(); ++name) {
+ OptionContainerPtr opts = getAll(*name);
+ for (OptionContainer::const_iterator opt = opts->begin();
+ opt != opts->end(); ++opt) {
+ // Get and fill the map for this option
+ ElementPtr map = Element::createMap();
+ // First set space from parent iterator
+ map->set("space", Element::create(*name));
+ // Set the code
+ uint16_t code = opt->option_->getType();
+ map->set("code", Element::create(code));
+ // Set the name (always for standard options else when asked for)
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(*name, code);
+ if (!def) {
+ def = LibDHCP::getRuntimeOptionDef(*name, code);
+ }
+ if (def) {
+ map->set("name", Element::create(def->getName()));
+ }
+ // Set the data item
+ if (!opt->formatted_value_.empty()) {
+ map->set("csv-format", Element::create(true));
+ map->set("data", Element::create(opt->formatted_value_));
+ } else {
+ map->set("csv-format", Element::create(false));
+ std::vector<uint8_t> bin = opt->option_->toBinary();
+ std::string repr = util::encode::encodeHex(bin);
+ map->set("data", Element::create(repr));
+ }
+ // Set the persistency flag
+ map->set("always-send", Element::create(opt->persistent_));
+ // Push on the list
+ result->add(map);
+ }
+ }
+ // Iterate first on vendor_options using vendor ids
+ const std::list<uint32_t>& ids = vendor_options_.getOptionSpaceNames();
+ for (std::list<uint32_t>::const_iterator id = ids.begin();
+ id != ids.end(); ++id) {
+ OptionContainerPtr opts = getAll(*id);
+ for (OptionContainer::const_iterator opt = opts->begin();
+ opt != opts->end(); ++opt) {
+ // Get and fill the map for this option
+ ElementPtr map = Element::createMap();
+ // First set space from parent iterator
+ std::ostringstream oss;
+ oss << "vendor-" << *id;
+ map->set("space", Element::create(oss.str()));
+ // Set the code
+ uint16_t code = opt->option_->getType();
+ map->set("code", Element::create(code));
+ // Set the name
+ Option::Universe universe = opt->option_->getUniverse();
+ OptionDefinitionPtr def =
+ LibDHCP::getVendorOptionDef(universe, *id, code);
+ if (!def) {
+ // vendor-XXX space is in oss
+ def = LibDHCP::getRuntimeOptionDef(oss.str(), code);
+ }
+ if (def) {
+ map->set("name", Element::create(def->getName()));
+ }
+ // Set the data item
+ if (!opt->formatted_value_.empty()) {
+ map->set("csv-format", Element::create(true));
+ map->set("data", Element::create(opt->formatted_value_));
+ } else {
+ map->set("csv-format", Element::create(false));
+ std::vector<uint8_t> bin = opt->option_->toBinary();
+ std::string repr = util::encode::encodeHex(bin);
+ map->set("data", Element::create(repr));
+ }
+ // Set the persistency flag
+ map->set("always-send", Element::create(opt->persistent_));
+ // Push on the list
+ result->add(map);
+ }
+ }
+ return (result);
+}
+
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h
index 61f3f13318..b0d527cfd3 100644
--- a/src/lib/dhcpsrv/cfg_option.h
+++ b/src/lib/dhcpsrv/cfg_option.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
#include <dhcp/option.h>
#include <dhcp/option_space_container.h>
+#include <cc/cfg_to_element.h>
#include <dhcpsrv/key_from_key.h>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
@@ -183,6 +184,11 @@ typedef std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> OptionContainerTypeRange;
/// Type of the index #2 - option persistency flag.
typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
+/// Pair of iterators to represent the range of options having the
+/// same persistency flag. The first element in this pair represents
+/// the beginning of the range, the second element represents the end.
+typedef std::pair<OptionContainerPersistIndex::const_iterator,
+ OptionContainerPersistIndex::const_iterator> OptionContainerPersistRange;
/// @brief Represents option data configuration for the DHCP server.
///
@@ -210,7 +216,7 @@ typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
/// options is useful when the client requests stateless configuration from
/// the DHCP server and no subnet is selected for this client. This client
/// will only receive global options.
-class CfgOption {
+class CfgOption : public isc::data::CfgToElement {
public:
/// @brief default constructor
@@ -316,7 +322,7 @@ public:
/// @brief Returns all options for the specified option space.
///
/// This method will not return vendor options, i.e. having option space
- /// name in the format of "vendor-X" where X is 32-bit unsiged integer.
+ /// name in the format of "vendor-X" where X is 32-bit unsigned integer.
/// See @c getAll(uint32_t) for vendor options.
///
/// @param option_space Name of the option space.
@@ -394,6 +400,11 @@ public:
/// @return List comprising option space names for vendor options.
std::list<std::string> getVendorIdsSpaceNames() const;
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// @brief Appends encapsulated options to the options in an option space.
diff --git a/src/lib/dhcpsrv/cfg_option_def.cc b/src/lib/dhcpsrv/cfg_option_def.cc
index 1dd51a5729..3a61bb1ef6 100644
--- a/src/lib/dhcpsrv/cfg_option_def.cc
+++ b/src/lib/dhcpsrv/cfg_option_def.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,9 +6,13 @@
#include <config.h>
#include <dhcp/libdhcp++.h>
+#include <dhcp/option_data_types.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/cfg_option_def.h>
+#include <sstream>
+
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -38,7 +42,7 @@ CfgOptionDef::equals(const CfgOptionDef& other) const {
// Get option space names held by the other object.
const std::list<std::string>&
other_names = other.option_definitions_.getOptionSpaceNames();
- // Compareg that sizes are the same. If they hold different number of
+ // Compare that sizes are the same. If they hold different number of
// option space names the objects are not equal.
if (names.size() != other_names.size()) {
return (false);
@@ -148,5 +152,58 @@ CfgOptionDef::get(const std::string& option_space,
return (OptionDefinitionPtr());
}
+ElementPtr
+CfgOptionDef::toElement() const {
+ // option-defs value is a list of maps
+ ElementPtr result = Element::createList();
+ // Iterate through the container by names and definitions
+ const std::list<std::string>& names =
+ option_definitions_.getOptionSpaceNames();
+ for (std::list<std::string>::const_iterator name = names.begin();
+ name != names.end(); ++name) {
+ OptionDefContainerPtr defs = getAll(*name);
+ for (OptionDefContainer::const_iterator def = defs->begin();
+ def != defs->end(); ++def) {
+ // Get and fill the map for this definition
+ ElementPtr map = Element::createMap();
+ // First set space from parent iterator
+ map->set("space", Element::create(*name));
+ // Set required items: name, code and type
+ map->set("name", Element::create((*def)->getName()));
+ map->set("code", Element::create((*def)->getCode()));
+ std::string data_type =
+ OptionDataTypeUtil::getDataTypeName((*def)->getType());
+ map->set("type", Element::create(data_type));
+ // Set the array type
+ bool array_type = (*def)->getArrayType();
+ map->set("array", Element::create(array_type));
+ // Set the encapsulate space
+ std::string encapsulates = (*def)->getEncapsulatedSpace();
+ map->set("encapsulate", Element::create(encapsulates));
+ // Set the record field types
+ OptionDefinition::RecordFieldsCollection fields =
+ (*def)->getRecordFields();
+ if (!fields.empty()) {
+ std::ostringstream oss;
+ for (OptionDefinition::RecordFieldsCollection::const_iterator
+ field = fields.begin();
+ field != fields.end(); ++field) {
+ if (field != fields.begin()) {
+ oss << ", ";
+ }
+ oss << OptionDataTypeUtil::getDataTypeName(*field);
+ }
+ map->set("record-types", Element::create(oss.str()));
+ } else {
+ map->set("record-types", Element::create(std::string()));
+ }
+ // Push on the list
+ result->add(map);
+ }
+ }
+ return (result);
+}
+
+
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cfg_option_def.h b/src/lib/dhcpsrv/cfg_option_def.h
index 1b660d689f..69e18fca97 100644
--- a/src/lib/dhcpsrv/cfg_option_def.h
+++ b/src/lib/dhcpsrv/cfg_option_def.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
#include <dhcp/option_definition.h>
#include <dhcp/option_space_container.h>
+#include <cc/cfg_to_element.h>
#include <string>
namespace isc {
@@ -26,7 +27,7 @@ namespace dhcp {
/// following names: "dhcp4" and "dhcp6" are reserved, though. They are
/// names of option spaces used for standard top-level DHCPv4 and DHCPv6
/// options respectively.
-class CfgOptionDef {
+class CfgOptionDef : public isc::data::CfgToElement {
public:
/// @brief Copies this configuration to a new configuration.
@@ -104,7 +105,7 @@ public:
/// @brief Return option definition for the particular option space and name.
///
- /// @param option_space pption space.
+ /// @param option_space option space.
/// @param option_name option name.
///
/// @return An option definition or NULL pointer if option definition
@@ -117,6 +118,11 @@ public:
return (option_definitions_);
}
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// @brief A collection of option definitions.
diff --git a/src/lib/dhcpsrv/cfg_rsoo.cc b/src/lib/dhcpsrv/cfg_rsoo.cc
index fbcbd90048..b54262e57f 100644
--- a/src/lib/dhcpsrv/cfg_rsoo.cc
+++ b/src/lib/dhcpsrv/cfg_rsoo.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,6 +6,9 @@
#include <dhcp/dhcp6.h>
#include <dhcpsrv/cfg_rsoo.h>
+#include <boost/lexical_cast.hpp>
+
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -33,6 +36,17 @@ CfgRSOO::enable(const uint16_t code) {
}
}
+ElementPtr
+CfgRSOO::toElement() const {
+ ElementPtr result = Element::createList();
+ // We can use LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, *opt) too...
+ for (std::set<uint16_t>::const_iterator opt = rsoo_options_.cbegin();
+ opt != rsoo_options_.cend(); ++opt) {
+ const std::string& code = boost::lexical_cast<std::string>(*opt);
+ result->add(Element::create(code));
+ }
+ return (result);
+}
}
}
diff --git a/src/lib/dhcpsrv/cfg_rsoo.h b/src/lib/dhcpsrv/cfg_rsoo.h
index bd5a87db13..01979e5385 100644
--- a/src/lib/dhcpsrv/cfg_rsoo.h
+++ b/src/lib/dhcpsrv/cfg_rsoo.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
#ifndef CFG_RSOO_H
#define CFG_RSOO_H
+#include <cc/cfg_to_element.h>
#include <boost/shared_ptr.hpp>
#include <stdint.h>
#include <set>
@@ -21,7 +22,7 @@ namespace dhcp {
/// 65 is officially RSSO-enabled. The list may be extended in the future
/// and this class allows for specifying any future RSOO-enabled options.
/// The administrator may also use existing options as RSOO-enabled.
-class CfgRSOO {
+class CfgRSOO : public isc::data::CfgToElement {
public:
/// @brief Constructor.
@@ -46,6 +47,11 @@ public:
/// @param code option to be enabled in RSOO
void enable(const uint16_t code);
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// @brief Contains a set of options that are allowed in RSOO option
diff --git a/src/lib/dhcpsrv/cfg_subnets4.cc b/src/lib/dhcpsrv/cfg_subnets4.cc
index f1c7a8f658..6c4b4f9afb 100644
--- a/src/lib/dhcpsrv/cfg_subnets4.cc
+++ b/src/lib/dhcpsrv/cfg_subnets4.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,25 +13,60 @@
#include <dhcpsrv/addr_utilities.h>
#include <asiolink/io_address.h>
#include <stats/stats_mgr.h>
+#include <sstream>
using namespace isc::asiolink;
+using namespace isc::data;
namespace isc {
namespace dhcp {
void
CfgSubnets4::add(const Subnet4Ptr& subnet) {
- /// @todo: Check that this new subnet does not cross boundaries of any
- /// other already defined subnet.
- if (isDuplicate(*subnet)) {
+ if (getBySubnetId(subnet->getID())) {
isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv4 subnet '"
<< subnet->getID() << "' is already in use");
+
+ } else if (getByPrefix(subnet->toText())) {
+ /// @todo: Check that this new subnet does not cross boundaries of any
+ /// other already defined subnet.
+ isc_throw(isc::dhcp::DuplicateSubnetID, "subnet with the prefix of '"
+ << subnet->toText() << "' already exists");
}
+
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4)
.arg(subnet->toText());
subnets_.push_back(subnet);
}
+void
+CfgSubnets4::del(const ConstSubnet4Ptr& subnet) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet->getID());
+ if (subnet_it == index.end()) {
+ isc_throw(BadValue, "no subnet with ID of '" << subnet->getID()
+ << "' found");
+ }
+ index.erase(subnet_it);
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DEL_SUBNET4)
+ .arg(subnet->toText());
+}
+
+ConstSubnet4Ptr
+CfgSubnets4::getBySubnetId(const SubnetID& subnet_id) const {
+ const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr());
+}
+
+ConstSubnet4Ptr
+CfgSubnets4::getByPrefix(const std::string& subnet_text) const {
+ const auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_text);
+ return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr());
+}
+
Subnet4Ptr
CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const {
@@ -85,7 +120,7 @@ CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
// If relayed message has been received, try to match the giaddr with the
// relay address specified for a subnet. It is also possible that the relay
- // address will not match with any of the relay addresses accross all
+ // address will not match with any of the relay addresses across all
// subnets, but we need to verify that for all subnets before we can try
// to use the giaddr to match with the subnet prefix.
if (!selector.giaddr_.isV4Zero()) {
@@ -194,6 +229,19 @@ CfgSubnets4::selectSubnet(const std::string& iface,
}
Subnet4Ptr
+CfgSubnets4::getSubnet(const SubnetID id) const {
+
+ /// @todo: Once this code is migrated to multi-index container, use
+ /// an index rather than full scan.
+ for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ++subnet) {
+ if ((*subnet)->getID() == id) {
+ return (*subnet);
+ }
+ }
+ return (Subnet4Ptr());
+}
+
+Subnet4Ptr
CfgSubnets4::selectSubnet(const IOAddress& address,
const ClientClasses& client_classes) const {
for (Subnet4Collection::const_iterator subnet = subnets_.begin();
@@ -217,17 +265,6 @@ CfgSubnets4::selectSubnet(const IOAddress& address,
return (Subnet4Ptr());
}
-bool
-CfgSubnets4::isDuplicate(const Subnet4& subnet) const {
- for (Subnet4Collection::const_iterator subnet_it = subnets_.begin();
- subnet_it != subnets_.end(); ++subnet_it) {
- if ((*subnet_it)->getID() == subnet.getID()) {
- return (true);
- }
- }
- return (false);
-}
-
void
CfgSubnets4::removeStatistics() {
using namespace isc::stats;
@@ -248,6 +285,9 @@ CfgSubnets4::removeStatistics() {
stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
"declined-reclaimed-addresses"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
}
}
@@ -273,5 +313,16 @@ CfgSubnets4::updateStatistics() {
}
}
+ElementPtr
+CfgSubnets4::toElement() const {
+ ElementPtr result = Element::createList();
+ // Iterate subnets
+ for (Subnet4Collection::const_iterator subnet = subnets_.cbegin();
+ subnet != subnets_.cend(); ++subnet) {
+ result->add((*subnet)->toElement());
+ }
+ return (result);
+}
+
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cfg_subnets4.h b/src/lib/dhcpsrv/cfg_subnets4.h
index f9f7f11c34..16ecff5379 100644
--- a/src/lib/dhcpsrv/cfg_subnets4.h
+++ b/src/lib/dhcpsrv/cfg_subnets4.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,9 +8,12 @@
#define CFG_SUBNETS4_H
#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
#include <dhcpsrv/subnet_selector.h>
#include <boost/shared_ptr.hpp>
+#include <string>
namespace isc {
namespace dhcp {
@@ -25,7 +28,7 @@ namespace dhcp {
///
/// See @c CfgSubnets4::selectSubnet documentation for more details on how the
/// subnet is selected for the client.
-class CfgSubnets4 {
+class CfgSubnets4 : public isc::data::CfgToElement {
public:
/// @brief Adds new subnet to the configuration.
@@ -36,6 +39,13 @@ public:
/// duplicates id of an existing subnet.
void add(const Subnet4Ptr& subnet);
+ /// @brief Removes subnet from the configuration.
+ ///
+ /// @param subnet Pointer to the subnet to be removed.
+ ///
+ /// @throw isc::BadValue if such subnet doesn't exist.
+ void del(const ConstSubnet4Ptr& subnet);
+
/// @brief Returns pointer to the collection of all IPv4 subnets.
///
/// This is used in a hook (subnet4_select), where the hook is able
@@ -47,6 +57,40 @@ public:
return (&subnets_);
}
+ /// @brief Returns const pointer to a subnet identified by the specified
+ /// subnet identifier.
+ ///
+ /// The const pointer is returned by this method to prevent a caller from
+ /// modifying the subnet configuration. Modifications to subnet configuration
+ /// is dangerous and must be done carefully. The subnets' configruation is
+ /// held in the multi index container and any modifications to the subnet
+ /// id or subnet prefix must trigger re-indexing of multi index container.
+ /// There is no possibility to enforce this when the non-const pointer is
+ /// returned.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Pointer to the @c Subnet4 object or null pointer if such
+ /// subnet doesn't exist.
+ ConstSubnet4Ptr getBySubnetId(const SubnetID& subnet_id) const;
+
+ /// @brief Returns const pointer to a subnet which matches the specified
+ /// prefix in the canonical form.
+ ///
+ /// The const pointer is returned by this method to prevent a caller from
+ /// modifying the subnet configuration. Modifications to subnet configuration
+ /// is dangerous and must be done carefully. The subnets' configruation is
+ /// held in the multi index container and any modifications to the subnet
+ /// id or subnet prefix must trigger re-indexing of multi index container.
+ /// There is no possibility to enforce this when the non-const pointer is
+ /// returned.
+ ///
+ /// @param subnet_prefix Subnet prefix, e.g. 10.2.3.0/24
+ ///
+ /// @return Pointer to the @c Subnet4 object or null pointer if such
+ /// subnet doesn't exist.
+ ConstSubnet4Ptr getByPrefix(const std::string& subnet_prefix) const;
+
/// @brief Returns a pointer to the selected subnet.
///
/// This method tries to retrieve the subnet for the client using various
@@ -81,7 +125,7 @@ public:
///
/// @todo This method requires performance improvement! It currently
/// iterates over all existing subnets (possibly a couple of times)
- /// to find the one which fulfils the search criteria. The subnet storage
+ /// to find the one which fulfills the search criteria. The subnet storage
/// is implemented as a simple STL vector which precludes fast searches
/// using specific keys. Hence, full scan is required. To improve the
/// search performance a different container type is required, e.g.
@@ -96,6 +140,14 @@ public:
/// or they are insufficient to select a subnet.
Subnet4Ptr selectSubnet(const SubnetSelector& selector) const;
+ /// @brief Returns subnet with specified subnet-id value
+ ///
+ /// Warning: this method uses full scan. Its use is not recommended for
+ /// packet processing.
+ ///
+ /// @return Subnet (or NULL)
+ Subnet4Ptr getSubnet(const SubnetID id) const;
+
/// @brief Returns a pointer to a subnet if provided address is in its range.
///
/// This method returns a pointer to the subnet if the address passed in
@@ -104,7 +156,7 @@ public:
/// @c selectSubnet(SubnetSelector).
///
/// @todo This method requires performance improvement! It currently
- /// iterates over all existing subnets to find the one which fulfils
+ /// iterates over all existing subnets to find the one which fulfills
/// the search criteria. The subnet storage is implemented as a simple
/// STL vector which precludes fast searches using specific keys.
/// Hence, full scan is required. To improve the search performance a
@@ -129,7 +181,7 @@ public:
/// @c selectSubnet(SubnetSelector).
///
/// @todo This method requires performance improvement! It currently
- /// iterates over all existing subnets to find the one which fulfils
+ /// iterates over all existing subnets to find the one which fulfills
/// the search criteria. The subnet storage is implemented as a simple
/// STL vector which precludes fast searches using specific keys.
/// Hence, full scan is required. To improve the search performance a
@@ -171,7 +223,7 @@ public:
/// This method updates statistics that are affected by the newly committed
/// configuration. In particular, it updates the number of available addresses
/// in each subnet. Other statistics may be added in the future. In general,
- /// these are statistics that are dependant only on configuration, so they are
+ /// these are statistics that are dependent only on configuration, so they are
/// not expected to change until the next reconfiguration event.
void updateStatistics();
@@ -183,15 +235,12 @@ public:
/// configuration and also subnet-ids may change.
void removeStatistics();
-private:
-
- /// @brief Checks that the IPv4 subnet with the given id already exists.
- ///
- /// @param subnet Subnet for which this function will check if the other
- /// subnet with equal id already exists.
+ /// @brief Unparse a configuration object
///
- /// @return true if the duplicate subnet exists.
- bool isDuplicate(const Subnet4& subnet) const;
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
/// @brief A container for IPv4 subnets.
Subnet4Collection subnets_;
diff --git a/src/lib/dhcpsrv/cfg_subnets6.cc b/src/lib/dhcpsrv/cfg_subnets6.cc
index 3136763f3c..e35b3773d4 100644
--- a/src/lib/dhcpsrv/cfg_subnets6.cc
+++ b/src/lib/dhcpsrv/cfg_subnets6.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,26 +9,63 @@
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/addr_utilities.h>
#include <stats/stats_mgr.h>
+#include <string.h>
+#include <sstream>
using namespace isc::asiolink;
+using namespace isc::data;
namespace isc {
namespace dhcp {
void
CfgSubnets6::add(const Subnet6Ptr& subnet) {
- /// @todo: Check that this new subnet does not cross boundaries of any
- /// other already defined subnet.
- if (isDuplicate(*subnet)) {
+ if (getBySubnetId(subnet->getID())) {
isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv6 subnet '"
<< subnet->getID() << "' is already in use");
+
+ } else if (getByPrefix(subnet->toText())) {
+ /// @todo: Check that this new subnet does not cross boundaries of any
+ /// other already defined subnet.
+ isc_throw(isc::dhcp::DuplicateSubnetID, "subnet with the prefix of '"
+ << subnet->toText() << "' already exists");
}
+
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
.arg(subnet->toText());
subnets_.push_back(subnet);
}
+void
+CfgSubnets6::del(const ConstSubnet6Ptr& subnet) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet->getID());
+ if (subnet_it == index.end()) {
+ isc_throw(BadValue, "no subnet with ID of '" << subnet->getID()
+ << "' found");
+ }
+ index.erase(subnet_it);
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DEL_SUBNET6)
+ .arg(subnet->toText());
+}
+
+ConstSubnet6Ptr
+CfgSubnets6::getBySubnetId(const SubnetID& subnet_id) const {
+ const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet6Ptr());
+}
+
+ConstSubnet6Ptr
+CfgSubnets6::getByPrefix(const std::string& subnet_text) const {
+ const auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_text);
+ return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet6Ptr());
+}
+
Subnet6Ptr
CfgSubnets6::selectSubnet(const SubnetSelector& selector) const {
Subnet6Ptr subnet;
@@ -162,15 +199,17 @@ CfgSubnets6::selectSubnet(const OptionPtr& interface_id,
return (Subnet6Ptr());
}
-bool
-CfgSubnets6::isDuplicate(const Subnet6& subnet) const {
- for (Subnet6Collection::const_iterator subnet_it = subnets_.begin();
- subnet_it != subnets_.end(); ++subnet_it) {
- if ((*subnet_it)->getID() == subnet.getID()) {
- return (true);
+Subnet6Ptr
+CfgSubnets6::getSubnet(const SubnetID id) const {
+
+ /// @todo: Once this code is migrated to multi-index container, use
+ /// an index rather than full scan.
+ for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ++subnet) {
+ if ((*subnet)->getID() == id) {
+ return (*subnet);
}
}
- return (false);
+ return (Subnet6Ptr());
}
void
@@ -197,6 +236,9 @@ CfgSubnets6::removeStatistics() {
stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
"declined-reclaimed-addresses"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
}
}
@@ -227,5 +269,16 @@ CfgSubnets6::updateStatistics() {
}
}
+ElementPtr
+CfgSubnets6::toElement() const {
+ ElementPtr result = Element::createList();
+ // Iterate subnets
+ for (Subnet6Collection::const_iterator subnet = subnets_.cbegin();
+ subnet != subnets_.cend(); ++subnet) {
+ result->add((*subnet)->toElement());
+ }
+ return (result);
+}
+
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cfg_subnets6.h b/src/lib/dhcpsrv/cfg_subnets6.h
index 4c59cb8887..5e03152275 100644
--- a/src/lib/dhcpsrv/cfg_subnets6.h
+++ b/src/lib/dhcpsrv/cfg_subnets6.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,10 +9,13 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <cc/cfg_to_element.h>
#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
#include <dhcpsrv/subnet_selector.h>
#include <util/optional_value.h>
#include <boost/shared_ptr.hpp>
+#include <string>
namespace isc {
namespace dhcp {
@@ -26,7 +29,7 @@ namespace dhcp {
///
/// See @c CfgSubnets6::selectSubnet documentation for more details on how the subnet
/// is selected for the client.
-class CfgSubnets6 {
+class CfgSubnets6 : public isc::data::CfgToElement {
public:
/// @brief Adds new subnet to the configuration.
@@ -37,6 +40,13 @@ public:
/// duplicates id of an existing subnet.
void add(const Subnet6Ptr& subnet);
+ /// @brief Removes subnet from the configuration.
+ ///
+ /// @param subnet Pointer to the subnet to be removed.
+ ///
+ /// @throw isc::BadValue if such subnet doesn't exist.
+ void del(const ConstSubnet6Ptr& subnet);
+
/// @brief Returns pointer to the collection of all IPv6 subnets.
///
/// This is used in a hook (subnet6_select), where the hook is able
@@ -48,6 +58,40 @@ public:
return (&subnets_);
}
+ /// @brief Returns const pointer to a subnet identified by the specified
+ /// subnet identifier.
+ ///
+ /// The const pointer is returned by this method to prevent a caller from
+ /// modifying the subnet configuration. Modifications to subnet configuration
+ /// is dangerous and must be done carefully. The subnets' configuration is
+ /// held in the multi index container and any modifications to the subnet
+ /// id or subnet prefix must trigger re-indexing of multi index container.
+ /// There is no possibility to enforce this when the non-const pointer is
+ /// returned.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Pointer to the @c Subnet6 object or null pointer if such
+ /// subnet doesn't exist.
+ ConstSubnet6Ptr getBySubnetId(const SubnetID& subnet_id) const;
+
+ /// @brief Returns const pointer to a subnet which matches the specified
+ /// prefix in the canonical form.
+ ///
+ /// The const pointer is returned by this method to prevent a caller from
+ /// modifying the subnet configuration. Modifications to subnet configuration
+ /// is dangerous and must be done carefully. The subnets' configruation is
+ /// held in the multi index container and any modifications to the subnet
+ /// id or subnet prefix must trigger re-indexing of multi index container.
+ /// There is no possibility to enforce this when the non-const pointer is
+ /// returned.
+ ///
+ /// @param subnet_prefix Subnet prefix, e.g. 2001:db8:1::/64
+ ///
+ /// @return Pointer to the @c Subnet6 object or null pointer if such
+ /// subnet doesn't exist.
+ ConstSubnet6Ptr getByPrefix(const std::string& subnet_prefix) const;
+
/// @brief Selects a subnet using parameters specified in the selector.
///
/// This method tries to retrieve the subnet for the client using various
@@ -73,7 +117,7 @@ public:
///
/// @todo This method requires performance improvement! It currently
/// iterates over all existing subnets (possibly a couple of times)
- /// to find the one which fulfils the search criteria. The subnet storage
+ /// to find the one which fulfills the search criteria. The subnet storage
/// is implemented as a simple STL vector which precludes fast searches
/// using specific keys. Hence, full scan is required. To improve the
/// search performance a different container type is required, e.g.
@@ -86,6 +130,14 @@ public:
/// @return Pointer to the selected subnet or NULL if no subnet found.
Subnet6Ptr selectSubnet(const SubnetSelector& selector) const;
+ /// @brief Returns subnet with specified subnet-id value
+ ///
+ /// Warning: this method uses full scan. Its use is not recommended for
+ /// packet processing.
+ ///
+ /// @return Subnet (or NULL)
+ Subnet6Ptr getSubnet(const SubnetID id) const;
+
/// @brief Selects the subnet using a specified address.
///
/// This method searches for the subnet using the specified address. If
@@ -106,7 +158,7 @@ public:
///
/// @todo This method requires performance improvement! It currently
/// iterates over all existing subnets (possibly a couple of times)
- /// to find the one which fulfils the search criteria. The subnet storage
+ /// to find the one which fulfills the search criteria. The subnet storage
/// is implemented as a simple STL vector which precludes fast searches
/// using specific keys. Hence, full scan is required. To improve the
/// search performance a different container type is required, e.g.
@@ -129,7 +181,7 @@ public:
/// This method updates statistics that are affected by the newly committed
/// configuration. In particular, it updates the number of available addresses
/// and prefixes in each subnet. Other statistics may be added in the future. In
- /// general, these are statistics that are dependant only on configuration, so
+ /// general, these are statistics that are dependent only on configuration, so
/// they are not expected to change until the next reconfiguration event.
void updateStatistics();
@@ -141,6 +193,11 @@ public:
/// configuration and also subnet-ids may change.
void removeStatistics();
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// @brief Selects a subnet using the interface name.
@@ -150,7 +207,7 @@ private:
/// name, the subnet is returned.
///
/// @todo This method requires performance improvement! It currently
- /// iterates over all existing subnets to find the one which fulfils
+ /// iterates over all existing subnets to find the one which fulfills
/// the search criteria. The subnet storage is implemented as a
/// simple STL vector which precludes fast searches using specific
/// keys. Hence, full scan is required. To improve the search
@@ -174,7 +231,7 @@ private:
/// subnet is returned.
///
/// @todo This method requires performance improvement! It currently
- /// iterates over all existing subnets to find the one which fulfils
+ /// iterates over all existing subnets to find the one which fulfills
/// the search criteria. The subnet storage is implemented as a
/// simple STL vector which precludes fast searches using specific
/// keys. Hence, full scan is required. To improve the search
@@ -191,14 +248,6 @@ private:
selectSubnet(const OptionPtr& interface_id,
const ClientClasses& client_classes) const;
- /// @brief Checks that the IPv6 subnet with the given id already exists.
- ///
- /// @param subnet Subnet for which this function will check if the other
- /// subnet with equal id already exists.
- ///
- /// @return true if the duplicate subnet exists.
- bool isDuplicate(const Subnet6& subnet) const;
-
/// @brief A container for IPv6 subnets.
Subnet6Collection subnets_;
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 6f37ffafde..2896b8bc4a 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,14 +10,11 @@
#include <dhcp/libdhcp++.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dhcpsrv_log.h>
-#include <dhcpsrv/subnet_id.h>
-#include <stats/stats_mgr.h>
#include <sstream>
#include <string>
using namespace isc::asiolink;
using namespace isc::util;
-using namespace isc::stats;
namespace isc {
namespace dhcp {
@@ -30,35 +27,6 @@ CfgMgr::instance() {
return (cfg_mgr);
}
-void
-CfgMgr::addOptionSpace4(const OptionSpacePtr& space) {
- if (!space) {
- isc_throw(InvalidOptionSpace,
- "provided option space object is NULL.");
- }
- OptionSpaceCollection::iterator it = spaces4_.find(space->getName());
- if (it != spaces4_.end()) {
- isc_throw(InvalidOptionSpace, "option space " << space->getName()
- << " already added.");
- }
- spaces4_.insert(make_pair(space->getName(), space));
-}
-
-void
-CfgMgr::addOptionSpace6(const OptionSpacePtr& space) {
- if (!space) {
- isc_throw(InvalidOptionSpace,
- "provided option space object is NULL.");
- }
- OptionSpaceCollection::iterator it = spaces6_.find(space->getName());
- if (it != spaces6_.end()) {
- isc_throw(InvalidOptionSpace, "option space " << space->getName()
- << " already added.");
- }
- spaces6_.insert(make_pair(space->getName(), space));
-}
-
-
std::string CfgMgr::getDataDir() const {
return (datadir_);
}
@@ -68,21 +36,19 @@ CfgMgr::setDataDir(const std::string& datadir) {
datadir_ = datadir;
}
-bool
-CfgMgr::isDuplicate(const Subnet6& subnet) const {
- for (Subnet6Collection::const_iterator subnet_it = subnets6_.begin();
- subnet_it != subnets6_.end(); ++subnet_it) {
- if ((*subnet_it)->getID() == subnet.getID()) {
- return (true);
- }
- }
- return (false);
-}
-
-
void
CfgMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
+ ensureCurrentAllocated();
+ // Note that D2ClientMgr::setD2Config() actually attempts to apply the
+ // configuration by stopping its sender and opening a new one and so
+ // forth per the new configuration.
d2_client_mgr_.setD2ClientConfig(new_config);
+
+ // Manager will throw if the set fails, if it succeeds
+ // we'll update our SrvConfig, configuration_, with the D2ClientConfig
+ // used. This is largely bookkeeping in case we ever want to compare
+ // configuration_ to another SrvConfig.
+ configuration_->setD2ClientConfig(new_config);
}
bool
@@ -184,7 +150,7 @@ CfgMgr::revert(const size_t index) {
commit();
}
-ConstSrvConfigPtr
+SrvConfigPtr
CfgMgr::getCurrentCfg() {
ensureCurrentAllocated();
return (configuration_);
@@ -201,8 +167,8 @@ CfgMgr::getStagingCfg() {
}
CfgMgr::CfgMgr()
- : datadir_(DHCP_DATA_DIR), echo_v4_client_id_(true),
- d2_client_mgr_(), verbose_mode_(false) {
+ : datadir_(DHCP_DATA_DIR), d2_client_mgr_(),
+ verbose_mode_(false), family_(AF_INET) {
// DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
// Note: the definition of DHCP_DATA_DIR needs to include quotation marks
// See AM_CPPFLAGS definition in Makefile.am
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 9a95b25d98..2afccb15b8 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,7 +13,6 @@
#include <dhcp/classify.h>
#include <dhcpsrv/d2_client_mgr.h>
#include <dhcpsrv/pool.h>
-#include <dhcpsrv/subnet.h>
#include <dhcpsrv/srv_config.h>
#include <util/buffer.h>
@@ -41,12 +40,9 @@ public:
/// @brief Configuration Manager
///
/// This singleton class holds the whole configuration for DHCPv4 and DHCPv6
-/// servers. It currently holds information about zero or more subnets6.
-/// Each subnet may contain zero or more pools. Pool4 and Pool6 is the most
-/// basic "chunk" of configuration. It contains a range of assignable
-/// addresses.
+/// servers.
///
-/// Below is a sketch of configuration inheritance (not implemented yet).
+/// Below is a sketch of configuration inheritance.
/// Let's investigate the following configuration:
///
/// @code
@@ -67,13 +63,9 @@ public:
/// and a valid lifetime of 1000s. The second subnet has preferred lifetime
/// of 500s, but valid lifetime of 2000s.
///
-/// Parameter inheritance is likely to be implemented in configuration handling
-/// routines, so there is no storage capability in a global scope for
-/// subnet-specific parameters.
-///
-/// @todo: Implement Subnet4 support (ticket #2237)
-/// @todo: Implement option definition support
-/// @todo: Implement parameter inheritance
+/// Parameter inheritance is implemented in dedicated classes. See
+/// @ref isc::dhcp::SimpleParser4::deriveParameters and
+/// @ref isc::dhcp::SimpleParser6::deriveParameters.
class CfgMgr : public boost::noncopyable {
public:
@@ -88,39 +80,9 @@ public:
/// accessing it.
static CfgMgr& instance();
- /// @brief Adds new DHCPv4 option space to the collection.
- ///
- /// @param space option space to be added.
- ///
- /// @throw isc::dhcp::InvalidOptionSpace invalid option space
- /// has been specified.
- void addOptionSpace4(const OptionSpacePtr& space);
-
- /// @brief Adds new DHCPv6 option space to the collection.
- ///
- /// @param space option space to be added.
- ///
- /// @throw isc::dhcp::InvalidOptionSpace invalid option space
- /// has been specified.
- void addOptionSpace6(const OptionSpacePtr& space);
-
- /// @brief Return option spaces for DHCPv4.
- ///
- /// @return A collection of option spaces.
- const OptionSpaceCollection& getOptionSpaces4() const {
- return (spaces4_);
- }
-
- /// @brief Return option spaces for DHCPv6.
- ///
- /// @return A collection of option spaces.
- const OptionSpaceCollection& getOptionSpaces6() const {
- return (spaces6_);
- }
-
/// @brief returns path do the data directory
///
- /// This method returns a path to writeable directory that DHCP servers
+ /// This method returns a path to writable directory that DHCP servers
/// can store data in.
/// @return data directory
std::string getDataDir() const;
@@ -130,24 +92,13 @@ public:
/// @param datadir New data directory.
void setDataDir(const std::string& datadir);
- /// @brief Sets whether server should send back client-id in DHCPv4
- ///
- /// This is a compatibility flag. The default (true) is compliant with
- /// RFC6842. False is for backward compatibility.
- ///
- /// @param echo should the client-id be sent or not
- void echoClientId(const bool echo) {
- echo_v4_client_id_ = echo;
- }
-
- /// @brief Returns whether server should send back client-id in DHCPv4.
- /// @return true if client-id should be returned, false otherwise.
- bool echoClientId() const {
- return (echo_v4_client_id_);
- }
-
/// @brief Updates the DHCP-DDNS client configuration to the given value.
///
+ /// Passes the new configuration to the D2ClientMgr instance,
+ /// d2_client_mgr_, which will attempt to apply the new configuration
+ /// by shutting down its sender and opening a new connection per the new
+ /// configuration (see @c D2ClientMgr::setD2ClientConfig()).
+ ///
/// @param new_config pointer to the new client configuration.
///
/// @throw Underlying method(s) will throw D2ClientError if given an empty
@@ -172,7 +123,7 @@ public:
/// @name Methods managing the collection of configurations.
///
/// The following methods manage the process of preparing a configuration
- /// without affecting a currently used configuration and then commiting
+ /// without affecting a currently used configuration and then committing
/// the configuration to replace current configuration atomically.
/// They also allow for keeping a history of previous configurations so
/// as the @c CfgMgr can revert to the historical configuration when
@@ -245,10 +196,26 @@ public:
///
/// This function returns pointer to the current configuration. If the
/// current configuration is not set it will create a default configuration
- /// and return it. Current configuration returned is read-only.
- ///
- /// @return Non-null const pointer to the current configuration.
- ConstSrvConfigPtr getCurrentCfg();
+ /// and return it.
+ ///
+ /// In the previous Kea releases this method used to return a const pointer
+ /// to the current configuration to ensure that it is not accidentally
+ /// modified while the server is running. This has been changed in Kea 1.3
+ /// release and now this function returns a non-const pointer. The reason
+ /// is that there are certain use cases when current configuration must
+ /// be modified without going through a full cycle of server
+ /// reconfiguration, e.g. add a subnet to the current configuration as
+ /// a result of receiving a command over control API. In such case the
+ /// performance of processing such command is critical and rebuilding the
+ /// whole configuration just for this small configuration change is out
+ /// of question.
+ ///
+ /// Nevertheless, such configuration updates should always be made with
+ /// caution and one has to make sure that the configuration data integrity
+ /// is preserved.
+ ///
+ /// @return Non-null pointer to the current configuration.
+ SrvConfigPtr getCurrentCfg();
/// @brief Returns a pointer to the staging configuration.
///
@@ -297,6 +264,16 @@ public:
return (default_logger_name_);
}
+ /// @brief Sets address family (AF_INET or AF_INET6)
+ void setFamily(uint16_t family) {
+ family_ = family == AF_INET ? AF_INET : AF_INET6;
+ }
+
+ /// @brief Returns address family.
+ uint16_t getFamily() const {
+ return (family_);
+ }
+
//@}
protected:
@@ -312,14 +289,6 @@ protected:
/// @brief virtual destructor
virtual ~CfgMgr();
- /// @brief a container for IPv6 subnets.
- ///
- /// That is a simple vector of pointers. It does not make much sense to
- /// optimize access time (e.g. using a map), because typical search
- /// pattern will use calling inRange() method on each subnet until
- /// a match is found.
- Subnet6Collection subnets6_;
-
private:
/// @brief Checks if current configuration is created and creates it if needed.
@@ -329,25 +298,9 @@ private:
/// default current configuration.
void ensureCurrentAllocated();
- /// @brief Checks that the IPv6 subnet with the given id already exists.
- ///
- /// @param subnet Subnet for which this function will check if the other
- /// subnet with equal id already exists.
- /// @return true if the duplicate subnet exists.
- bool isDuplicate(const Subnet6& subnet) const;
-
- /// @brief Container for defined DHCPv6 option spaces.
- OptionSpaceCollection spaces6_;
-
- /// @brief Container for defined DHCPv4 option spaces.
- OptionSpaceCollection spaces4_;
-
/// @brief directory where data files (e.g. server-id) are stored
std::string datadir_;
- /// Indicates whether v4 server should send back client-id
- bool echo_v4_client_id_;
-
/// @brief Manages the DHCP-DDNS client and its configuration.
D2ClientMgr d2_client_mgr_;
@@ -372,6 +325,9 @@ private:
/// @brief Default logger name.
std::string default_logger_name_;
+
+ /// @brief Address family.
+ uint16_t family_;
};
} // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/client_class_def.cc b/src/lib/dhcpsrv/client_class_def.cc
index 010457eaed..135982d708 100644
--- a/src/lib/dhcpsrv/client_class_def.cc
+++ b/src/lib/dhcpsrv/client_class_def.cc
@@ -1,12 +1,15 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#include "client_class_def.h"
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/cfgmgr.h>
#include <boost/foreach.hpp>
+using namespace isc::data;
+
namespace isc {
namespace dhcp {
@@ -74,6 +77,16 @@ ClientClassDef::setMatchExpr(const ExpressionPtr& match_expr) {
match_expr_ = match_expr;
}
+std::string
+ClientClassDef::getTest() const {
+ return (test_);
+}
+
+void
+ClientClassDef::setTest(const std::string& test) {
+ test_ = test;
+}
+
const CfgOptionPtr&
ClientClassDef::getCfgOption() const {
return (cfg_option_);
@@ -98,6 +111,31 @@ ClientClassDef::equals(const ClientClassDef& other) const {
(filename_ == other.filename_));
}
+ElementPtr
+ClientClassDef:: toElement() const {
+ uint16_t family = CfgMgr::instance().getFamily();
+ ElementPtr result = Element::createMap();
+ // Set name
+ result->set("name", Element::create(name_));
+ // Set original match expression (empty string won't parse)
+ if (!test_.empty()) {
+ result->set("test", Element::create(test_));
+ }
+ // Set option-data
+ result->set("option-data", cfg_option_->toElement());
+ if (family != AF_INET) {
+ // Other parameters are DHCPv4 specific
+ return (result);
+ }
+ // Set next-server
+ result->set("next-server", Element::create(next_server_.toText()));
+ // Set server-hostname
+ result->set("server-hostname", Element::create(sname_));
+ // Set boot-file-name
+ result->set("boot-file-name", Element::create(filename_));
+ return (result);
+}
+
std::ostream& operator<<(std::ostream& os, const ClientClassDef& x) {
os << "ClientClassDef:" << x.getName();
return (os);
@@ -123,11 +161,13 @@ ClientClassDictionary::~ClientClassDictionary() {
void
ClientClassDictionary::addClass(const std::string& name,
const ExpressionPtr& match_expr,
+ const std::string& test,
const CfgOptionPtr& cfg_option,
asiolink::IOAddress next_server,
const std::string& sname,
const std::string& filename) {
ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option));
+ cclass->setTest(test);
cclass->setNextServer(next_server);
cclass->setSname(sname);
cclass->setFilename(filename);
@@ -191,6 +231,16 @@ ClientClassDictionary::equals(const ClientClassDictionary& other) const {
return (true);
}
+ElementPtr
+ClientClassDictionary::toElement() const {
+ ElementPtr result = Element::createList();
+ // Iterate on the map
+ for (ClientClassDefMap::iterator this_class = classes_->begin();
+ this_class != classes_->end(); ++this_class) {
+ result->add(this_class->second->toElement());
+ }
+ return (result);
+}
} // namespace isc::dhcp
} // namespace isc
diff --git a/src/lib/dhcpsrv/client_class_def.h b/src/lib/dhcpsrv/client_class_def.h
index 51fb7e2544..8bc5ad72a2 100644
--- a/src/lib/dhcpsrv/client_class_def.h
+++ b/src/lib/dhcpsrv/client_class_def.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
#ifndef CLIENT_CLASS_DEF_H
#define CLIENT_CLASS_DEF_H
+#include <cc/cfg_to_element.h>
#include <dhcpsrv/cfg_option.h>
#include <eval/token.h>
#include <exceptions/exceptions.h>
@@ -37,7 +38,7 @@ public:
};
/// @brief Embodies a single client class definition
-class ClientClassDef {
+class ClientClassDef : public isc::data::CfgToElement {
public:
/// @brief Constructor
///
@@ -70,6 +71,14 @@ public:
/// @param match_expr the expression to assign the class
void setMatchExpr(const ExpressionPtr& match_expr);
+ /// @brief Fetches the class's original match expression
+ std::string getTest() const;
+
+ /// @brief Sets the class's original match expression
+ ///
+ /// @param test the original expression to assign the class
+ void setTest(const std::string& test);
+
/// @brief Fetches the class's option collection
const CfgOptionPtr& getCfgOption() const;
@@ -145,6 +154,11 @@ public:
return (filename_);
}
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// @brief Unique text identifier by which this class is known.
std::string name_;
@@ -153,6 +167,10 @@ private:
/// this class.
ExpressionPtr match_expr_;
+ /// @brief The original expression which determines membership in
+ /// this class.
+ std::string test_;
+
/// @brief The option data configuration for this class
CfgOptionPtr cfg_option_;
@@ -188,7 +206,7 @@ typedef boost::shared_ptr<ClientClassDefMap> ClientClassDefMapPtr;
typedef std::pair<std::string, ClientClassDefPtr> ClientClassMapPair;
/// @brief Maintains a list of ClientClassDef's
-class ClientClassDictionary {
+class ClientClassDictionary : public isc::data::CfgToElement {
public:
/// @brief Constructor
@@ -203,6 +221,7 @@ public:
///
/// @param name Name to assign to this class
/// @param match_expr Expression the class will use to determine membership
+ /// @param test Original version of match_expr
/// @param options Collection of options members should be given
/// @param next_server next-server value for this class (optional)
/// @param sname server-name value for this class (optional)
@@ -212,7 +231,7 @@ public:
/// dictionary. See @ref dhcp::ClientClassDef::ClientClassDef() for
/// others.
void addClass(const std::string& name, const ExpressionPtr& match_expr,
- const CfgOptionPtr& options,
+ const std::string& test, const CfgOptionPtr& options,
asiolink::IOAddress next_server = asiolink::IOAddress("0.0.0.0"),
const std::string& sname = std::string(),
const std::string& filename = std::string());
@@ -271,6 +290,11 @@ public:
return (!equals(other));
}
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// @brief Map of the class definitions
diff --git a/src/lib/dhcpsrv/cql_connection.cc b/src/lib/dhcpsrv/cql_connection.cc
index 1dec234ebf..6959d227a3 100644
--- a/src/lib/dhcpsrv/cql_connection.cc
+++ b/src/lib/dhcpsrv/cql_connection.cc
@@ -69,7 +69,7 @@ CqlConnection::openDatabase() {
const char* contact_points = "127.0.0.1";
string scontact_points;
try {
- scontact_points = getParameter("contact_points");
+ scontact_points = getParameter("contact-points");
contact_points = scontact_points.c_str();
} catch (...) {
// No host. Fine, we'll use "localhost".
diff --git a/src/lib/dhcpsrv/cql_lease_mgr.cc b/src/lib/dhcpsrv/cql_lease_mgr.cc
index f7486c6054..c3030f1b04 100644
--- a/src/lib/dhcpsrv/cql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/cql_lease_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 - 2016 Deutsche Telekom AG.
+// Copyright (C) 2015 - 2017 Deutsche Telekom AG.
//
// Author: Razvan Becheriu <razvan.becheriu@qualitance.com>
//
@@ -1885,6 +1885,16 @@ CqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
return (result);
}
+size_t
+CqlLeaseMgr::wipeLeases4(const SubnetID& /*subnet_id*/) {
+ isc_throw(NotImplemented, "wipeLeases4 is not implemented for Cassandra backend");
+}
+
+size_t
+CqlLeaseMgr::wipeLeases6(const SubnetID& /*subnet_id*/) {
+ isc_throw(NotImplemented, "wipeLeases6 is not implemented for Cassandra backend");
+}
+
std::string
CqlLeaseMgr::getName() const {
std::string name = "";
diff --git a/src/lib/dhcpsrv/cql_lease_mgr.h b/src/lib/dhcpsrv/cql_lease_mgr.h
index 76f96c859e..bff24148a4 100644
--- a/src/lib/dhcpsrv/cql_lease_mgr.h
+++ b/src/lib/dhcpsrv/cql_lease_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 - 2016 Deutsche Telekom AG.
+// Copyright (C) 2015 - 2017 Deutsche Telekom AG.
//
// Author: Razvan Becheriu <razvan.becheriu@qualitance.com>
//
@@ -379,6 +379,28 @@ public:
/// @return Number of leases deleted.
virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t );
+ /// @brief Removes specified IPv4 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @todo: Not implemented yet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases4(const SubnetID& subnet_id);
+
+ /// @brief Removed specified IPv6 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @todo: Not implemented yet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases6(const SubnetID& subnet_id);
+
/// @brief Return backend type
///
/// @return Type of the backend.
@@ -572,7 +594,7 @@ private:
///
/// This method performs the common actions for the various getLease4()
/// methods. It acts as an interface to the getLeaseCollection() method,
- /// but retrieveing only a single lease.
+ /// but retrieving only a single lease.
///
/// @param stindex Index of statement being executed
/// @param data array containing input parameters for the query
@@ -584,7 +606,7 @@ private:
///
/// This method performs the common actions for the various getLease4()
/// methods. It acts as an interface to the getLeaseCollection() method,
- /// but retrieveing only a single lease.
+ /// but retrieving only a single lease.
///
/// @param stindex Index of statement being executed
/// @param data array containing input parameters for the query
diff --git a/src/lib/dhcpsrv/d2_client_cfg.cc b/src/lib/dhcpsrv/d2_client_cfg.cc
index 55df0be0bf..14c45a5bc3 100644
--- a/src/lib/dhcpsrv/d2_client_cfg.cc
+++ b/src/lib/dhcpsrv/d2_client_cfg.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,6 +15,8 @@
#include <string>
using namespace std;
+using namespace isc::asiolink;
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -217,6 +219,41 @@ D2ClientConfig::toText() const {
return (stream.str());
}
+ElementPtr
+D2ClientConfig::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set enable-updates
+ result->set("enable-updates", Element::create(enable_updates_));
+ // Set qualifying-suffix
+ result->set("qualifying-suffix", Element::create(qualifying_suffix_));
+ // Set server-ip
+ result->set("server-ip", Element::create(server_ip_.toText()));
+ // Set server-port
+ result->set("server-port", Element::create(static_cast<long long>(server_port_)));
+ // Set sender-ip
+ result->set("sender-ip", Element::create(sender_ip_.toText()));
+ // Set sender-port
+ result->set("sender-port", Element::create(static_cast<long long>(sender_port_)));
+ // Set max-queue-size
+ result->set("max-queue-size", Element::create(static_cast<long long>(max_queue_size_)));
+ // Set ncr-protocol
+ result->set("ncr-protocol", Element::create(dhcp_ddns::ncrProtocolToString(ncr_protocol_)));
+ // Set ncr-format
+ result->set("ncr-format", Element::create(dhcp_ddns::ncrFormatToString(ncr_format_)));
+ // Set always-include-fqdn
+ result->set("always-include-fqdn", Element::create(always_include_fqdn_));
+ // Set override-no-update
+ result->set("override-no-update", Element::create(override_no_update_));
+ // Set override-client-update
+ result->set("override-client-update", Element::create(override_client_update_));
+ // Set replace-client-name
+ result->set("replace-client-name",
+ Element::create(replaceClientNameModeToString(replace_client_name_mode_)));
+ // Set generated-prefix
+ result->set("generated-prefix", Element::create(generated_prefix_));
+ return (result);
+}
+
std::ostream&
operator<<(std::ostream& os, const D2ClientConfig& config) {
os << config.toText();
diff --git a/src/lib/dhcpsrv/d2_client_cfg.h b/src/lib/dhcpsrv/d2_client_cfg.h
index b43327cd7f..99a357ca19 100644
--- a/src/lib/dhcpsrv/d2_client_cfg.h
+++ b/src/lib/dhcpsrv/d2_client_cfg.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,7 +11,9 @@
/// This file defines the classes Kea uses to manage configuration needed to
/// act as a client of the kea-dhcp-ddns module (aka D2).
///
+
#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
#include <dhcp_ddns/ncr_io.h>
#include <exceptions/exceptions.h>
@@ -46,10 +48,10 @@ public:
/// parameters associated with DHCP-DDNS and acting as a client of D2.
/// Instances of this class may be constructed through configuration parsing.
///
-class D2ClientConfig {
+class D2ClientConfig : public isc::data::CfgToElement {
public:
/// @brief Default configuration constants.
- /// @todo For now these are hard-coded as configuraiton layer cannot
+ /// @todo For now these are hard-coded as configuration layer cannot
/// readily provide them (see Trac #3358).
static const char* DFT_SERVER_IP;
static const size_t DFT_SERVER_PORT;
@@ -147,7 +149,7 @@ public:
return(sender_port_);
}
- /// @brief Return Maximun sender queue size
+ /// @brief Return Maximum sender queue size
size_t getMaxQueueSize() const {
return(max_queue_size_);
}
@@ -214,7 +216,7 @@ public:
///
/// @param mode_str text to convert to an enum.
/// Valid string values: "never", "always", "when-present",
- /// "when-not-present" (case insensistive)
+ /// "when-not-present" (case-insensitive)
///
/// @return NameChangeFormat value which maps to the given string.
///
@@ -230,6 +232,11 @@ public:
/// "unknown" if not.
static std::string replaceClientNameModeToString(const ReplaceClientNameMode& mode);
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
protected:
/// @brief Validates member values.
///
@@ -254,7 +261,7 @@ private:
/// @brief IP port on which the client should send
size_t sender_port_;
- /// @brief Maxium number of NCRs allowed to queue waiting to send
+ /// @brief Maximum number of NCRs allowed to queue waiting to send
size_t max_queue_size_;
/// @brief The socket protocol to use with kea-dhcp-ddns.
diff --git a/src/lib/dhcpsrv/d2_client_mgr.h b/src/lib/dhcpsrv/d2_client_mgr.h
index 511b910c7b..7a2de10d4c 100644
--- a/src/lib/dhcpsrv/d2_client_mgr.h
+++ b/src/lib/dhcpsrv/d2_client_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -202,7 +202,7 @@ public:
template <class T>
void adjustFqdnFlags(const T& fqdn, T& fqdn_resp);
- /// @brief Get direcional update flags based on server FQDN flags
+ /// @brief Get directional update flags based on server FQDN flags
///
/// Templated convenience method which determines whether forward and
/// reverse updates should be performed based on a server response version
@@ -213,9 +213,9 @@ public:
/// * reverse will be true if N_FLAG is false
///
/// @param fqdn_resp FQDN option from which to read server (outbound) flags
- /// @param [out] forward bool value will be set to true if forward udpates
+ /// @param [out] forward bool value will be set to true if forward updates
/// should be done, false if not.
- /// @param [out] reverse bool value will be set to true if reverse udpates
+ /// @param [out] reverse bool value will be set to true if reverse updates
/// should be done, false if not.
/// @tparam T FQDN Option class containing the FQDN data such as
/// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
@@ -258,7 +258,7 @@ public:
/// @param io_service IOService to be used for sender IO event processing
/// @warning It is up to the invoking layer to ensure the io_service
/// instance used outlives the D2ClientMgr send mode. When the send mode
- /// is exited, either expliclity by callind stopSender() or implicitly
+ /// is exited, either explicitly by callind stopSender() or implicitly
/// through D2CLientMgr destruction, any ASIO objects such as sockets or
/// timers will be closed and released. If the io_service goes out of scope
/// first this behavior could be unpredictable.
diff --git a/src/lib/dhcpsrv/daemon.cc b/src/lib/dhcpsrv/daemon.cc
index 3a9a0b52d5..fd06e8b732 100644
--- a/src/lib/dhcpsrv/daemon.cc
+++ b/src/lib/dhcpsrv/daemon.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -17,6 +17,7 @@
#include <boost/bind.hpp>
#include <sstream>
+#include <fstream>
#include <errno.h>
/// @brief provides default implementation for basic daemon operations
@@ -205,5 +206,31 @@ Daemon::createPIDFile(int pid) {
am_file_author_ = true;
}
+size_t
+Daemon::writeConfigFile(const std::string& config_file,
+ isc::data::ConstElementPtr cfg) const {
+ if (!cfg) {
+ cfg = CfgMgr::instance().getCurrentCfg()->toElement();
+ }
+
+ if (!cfg) {
+ isc_throw(Unexpected, "Can't write configuration: conversion to JSON failed");
+ }
+
+ std::ofstream out(config_file, std::ios::trunc);
+ if (!out.good()) {
+ isc_throw(Unexpected, "Unable to open file " + config_file + " for writing");
+ }
+
+ // Write the actual content using pretty printing.
+ isc::data::prettyPrint(cfg, out);
+
+ size_t bytes = static_cast<size_t>(out.tellp());
+
+ out.close();
+
+ return (bytes);
+}
+
};
};
diff --git a/src/lib/dhcpsrv/daemon.h b/src/lib/dhcpsrv/daemon.h
index 022b57abcb..df895ddd42 100644
--- a/src/lib/dhcpsrv/daemon.h
+++ b/src/lib/dhcpsrv/daemon.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -35,7 +35,7 @@ public:
/// implementations should derive from it.
///
/// Methods are not pure virtual, as we need to instantiate basic daemons (e.g.
-/// Dhcpv6Srv) in tests, without going through the hassles of implemeting stub
+/// Dhcpv6Srv) in tests, without going through the hassles of implementing stub
/// methods.
///
/// Classes derived from @c Daemon may install custom signal handlers using
@@ -55,7 +55,7 @@ public:
/// process to NULL.
Daemon();
- /// @brief Desctructor
+ /// @brief Destructor
///
/// Having virtual destructor ensures that all derived classes will have
/// virtual destructor as well.
@@ -100,7 +100,7 @@ public:
/// @brief Sets or clears verbose mode
///
- /// Verbose mode (-v in command-line) triggers loggers to log everythin
+ /// Verbose mode (-v in command-line) triggers loggers to log everything
/// (sets severity to DEBUG and debuglevel to 99). Values specified in the
/// config file are ignored.
///
@@ -136,6 +136,25 @@ public:
/// @param config_file pathname of the configuration file
void setConfigFile(const std::string& config_file);
+ /// @brief Writes current configuration to specified file
+ ///
+ /// This method writes the current configuration to specified file.
+ /// @todo: this logically more belongs to CPL process file. Once
+ /// Daemon is merged with CPL architecture, it will be a better
+ /// fit.
+ ///
+ /// If cfg is not specified, the current config (as returned by
+ /// CfgMgr::instance().getCurrentCfg() will be returned.
+ ///
+ /// @param config_file name of the file to write the configuration to
+ /// @param cfg configuration to write (optional)
+ /// @return number of files written
+ /// @throw Unexpected if CfgMgr can't retrieve configuration or file cannot
+ /// be written
+ virtual size_t
+ writeConfigFile(const std::string& config_file,
+ isc::data::ConstElementPtr cfg = isc::data::ConstElementPtr()) const;
+
/// @brief returns the process name
/// This value is used as when forming the default PID file name
/// @return text string
diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox
index e237666857..704557d324 100644
--- a/src/lib/dhcpsrv/database_backends.dox
+++ b/src/lib/dhcpsrv/database_backends.dox
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -93,7 +93,7 @@
@subsection dhcpdb-keywords-cql Cassandra (CQL) connection string keywords
- - <b>contact_points</b> - a list of comma separated IP addresses of the
+ - <b>contact-points</b> - a list of comma separated IP addresses of the
cluster contact points>
- <b>port</b> - an integer specifying a connection port. If not specified, the
default port will be used.
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.h b/src/lib/dhcpsrv/dhcpsrv_log.h
index b7141bc894..b4c41b1cb2 100644
--- a/src/lib/dhcpsrv/dhcpsrv_log.h
+++ b/src/lib/dhcpsrv/dhcpsrv_log.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -23,27 +23,27 @@ namespace dhcp {
/// @brief Traces normal operations
///
/// E.g. sending a query to the database etc.
-const int DHCPSRV_DBG_TRACE = DBGLVL_TRACE_BASIC;
+const int DHCPSRV_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
/// @brief Records the results of the lookups
///
/// Using the example of tracing queries from the backend database, this will
/// just record the summary results.
-const int DHCPSRV_DBG_RESULTS = DBGLVL_TRACE_BASIC_DATA;
+const int DHCPSRV_DBG_RESULTS = isc::log::DBGLVL_TRACE_BASIC_DATA;
/// @brief Additional information
///
/// Record detailed tracing. This is generally reserved for tracing access to
/// the lease database.
-const int DHCPSRV_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
+const int DHCPSRV_DBG_TRACE_DETAIL = isc::log::DBGLVL_TRACE_DETAIL;
/// @brief Additional information
///
/// Record detailed (and verbose) data on the server.
-const int DHCPSRV_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+const int DHCPSRV_DBG_TRACE_DETAIL_DATA = isc::log::DBGLVL_TRACE_DETAIL_DATA;
// Trace hook related operations
-const int DHCPSRV_DBG_HOOKS = DBGLVL_TRACE_BASIC;
+const int DHCPSRV_DBG_HOOKS = isc::log::DBGLVL_TRACE_BASIC;
///@}
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 9fa51bd16d..1f3961c23d 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,13 +6,6 @@
$NAMESPACE isc::dhcp
-% DHCPRSV_MEMFILE_CONVERTING_LEASE_FILES running LFC now to convert lease files to the current schema: %1.%2
-A warning message issued when the server has detected lease files that need
-to be either upgraded or downgraded to match the server's schema, and that
-the server is automatically running the LFC process to perform the conversion.
-This should only occur the first time the server is launched following a Kea
-installation upgrade (or downgrade).
-
% DHCPSRV_CFGMGR_ADD_IFACE listening on interface %1
An info message issued when a new interface is being added to the collection of
interfaces on which the server listens to DHCP messages.
@@ -51,11 +44,26 @@ there is a good reason for it, to avoid increased number of renewals and
a need for rebinding (increase of multicast traffic, which may be received
by multiple servers).
+% DHCPSRV_CFGMGR_DEL_SUBNET4 IPv4 subnet %1 removed
+This debug message is issued when a subnet is successfully removed from the
+server configuration. The argument identifies the subnet removed.
+
+% DHCPSRV_CFGMGR_DEL_SUBNET6 IPv6 subnet %1 removed
+This debug message is issued when a subnet is successfully removed from the
+
+% DHCPSRV_CFGMGR_NEW_SUBNET4 a new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified IPv4 subnet.
+
% DHCPSRV_CFGMGR_NO_SUBNET4 no suitable subnet is defined for address hint %1
This debug message is output when the DHCP configuration manager has received
a request for an IPv4 subnet for the specified address, but no such
subnet exists.
+% DHCPSRV_CFGMGR_NEW_SUBNET6 a new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified subnet.
+
% DHCPSRV_CFGMGR_NO_SUBNET6 no suitable subnet is defined for address hint %1
This debug message is output when the DHCP configuration manager has received
a request for an IPv6 subnet for the specified address, but no such
@@ -71,6 +79,11 @@ This is a debug message reporting that the DHCP configuration manager has
returned the specified IPv6 subnet when given the address hint specified
because it is the only subnet defined.
+% DHCPSRV_CFGMGR_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2
+This warning message is issued on an attempt to configure multiple options with the
+same option code for the particular subnet. Adding multiple options is uncommon
+for DHCPv6, but it is not prohibited.
+
% DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED use of raw sockets is unsupported on this OS, UDP sockets will be used
This warning message is logged when the user specified that the
DHCPv4 server should use the raw sockets to receive the DHCP
@@ -366,6 +379,13 @@ with the specified address to the memory file backend database.
The code has issued a commit call. For the memory file database, this is
a no-op.
+% DHCPSRV_MEMFILE_CONVERTING_LEASE_FILES running LFC now to convert lease files to the current schema: %1.%2
+A warning message issued when the server has detected lease files that need
+to be either upgraded or downgraded to match the server's schema, and that
+the server is automatically running the LFC process to perform the conversion.
+This should only occur the first time the server is launched following a Kea
+installation upgrade (or downgrade).
+
% DHCPSRV_MEMFILE_DB opening memory file lease database: %1
This informational message is logged when a DHCP server (either V4 or
V6) is about to open a memory file lease database. The parameters of
@@ -545,6 +565,27 @@ lease from the memory file database for the specified address.
A debug message issued when the server is attempting to update IPv6
lease from the memory file database for the specified address.
+% DHCPSRV_MEMFILE_WIPE_LEASES4 removing all IPv4 leases from subnet %1
+This informational message is printed when removal of all leases from
+specified IPv4 subnet is commencing. This is a result of receiving administrative
+command.
+
+% DHCPSRV_MEMFILE_WIPE_LEASES4_FINISHED removing all IPv4 leases from subnet %1 finished, removed %2 leases
+This informational message is printed when removal of all leases from
+a specified IPv4 subnet has finished. The number of removed leases is
+printed.
+
+% DHCPSRV_MEMFILE_WIPE_LEASES6 removing all IPv6 leases from subnet %1
+This informational message is printed when removal of all leases from
+specified IPv6 subnet is commencing. This is a result of receiving administrative
+command.
+
+% DHCPSRV_MEMFILE_WIPE_LEASES6_FINISHED removing all IPv6 leases from subnet %1 finished, removed %2 leases
+This informational message is printed when removal of all leases from
+a specified IPv6 subnet has finished. The number of removed leases is
+printed.
+
+
% DHCPSRV_MULTIPLE_RAW_SOCKETS_PER_IFACE current configuration will result in opening multiple broadcast capable sockets on some interfaces and some DHCP messages may be duplicated
A warning message issued when the current configuration indicates that multiple
sockets, capable of receiving broadcast traffic, will be opened on some of the
@@ -670,7 +711,7 @@ The code has issued a rollback call. All outstanding transaction will
be rolled back and not committed to the database.
% DHCPSRV_MYSQL_START_TRANSACTION starting new MySQL transaction
-A debug message issued whena new MySQL transaction is being started.
+A debug message issued when a new MySQL transaction is being started.
This message is typically not issued when inserting data into a
single table because the server doesn't explicitly start
transactions in this case. This message is issued when data is
@@ -844,14 +885,14 @@ includes the client identification information. The second argument
indicates whether the DNS entry is to be added or removed. The third
argument carries the details of the NameChangeRequest.
-% DHCPSRV_QUEUE_NCR_FAILED %1: queueing %2 name change request failed for lease %3: %4
+% DHCPSRV_QUEUE_NCR_FAILED %1: queuing %2 name change request failed for lease %3: %4
This error message is logged when sending a name change request
to DHCP DDNS failed. The first argument includes the client identification
information. The second argument indicates whether the DNS entry is to be
added or removed. The third argument specifies the leased address. The
last argument provides the reason for failure.
-% DHCPSRV_QUEUE_NCR_SKIP %1: skip queueing name change request for lease: %2
+% DHCPSRV_QUEUE_NCR_SKIP %1: skip queuing name change request for lease: %2
This debug message is issued when the server decides to not queue the name
change request because the lease doesn't include the FQDN, the forward and
reverse update is disabled for this lease or the DNS updates are disabled
@@ -878,45 +919,12 @@ An example of such operation is a periodic cleanup of
expired leases. The name of the timer is included in the
message.
-% DHCPSRV_TIMERMGR_SOCKET_CLEAR_FAILED clearing watch socket for timer %1 failed: %2
-An error message indicating that the specified timer elapsed,
-the operation associated with the timer was executed but the
-server was unable to signal this to the worker thread responsible
-for dispatching timers. The thread will continue but it will
-not be able to dispatch any operations for this timer. The
-server reconfiguration or restart may solve the problem
-but the situation may repeat.
-
-% DHCPSRV_TIMERMGR_SOCKET_MARK_FAILED marking watch socket for timer %1 failed: %2
-An error message indicating that the specified timer elapsed,
-but the server was unable to flag that the handler function
-should be executed for this timer. The callback will not
-be executed this time and most likely the subsequent attempts
-will not be successful too. This error is highly unlikely.
-The name of the timer and the reason for failure is included
-in the message.
-
-% DHCPSRV_TIMERMGR_START_THREAD starting thread for timers
-A debug message issued when the Timer Manager is starting a
-worker thread to run started timers. The worker thread is
-typically started right after all timers have been registered
-and runs until timers need to be reconfigured, e.g. their
-interval is changed, new timers are registered or existing
-timers are unregistered.
-
% DHCPSRV_TIMERMGR_START_TIMER starting timer: %1
A debug message issued when the registered interval timer is
being started. If this operation is successful the timer will
periodically execute the operation associated with it. The
name of the started timer is included in the message.
-% DHCPSRV_TIMERMGR_STOP_THREAD stopping thread for timers
-A debug message issued when the Timer Manager is stopping
-the worker thread which executes interval timers. When the
-thread is stopped no timers will be executed. The thread is
-typically stopped at the server reconfiguration or when the
-server shuts down.
-
% DHCPSRV_TIMERMGR_STOP_TIMER stopping timer: %1
A debug message issued when the registered interval timer is
being stopped. The timer remains registered and can be restarted
diff --git a/src/lib/dhcpsrv/host.cc b/src/lib/dhcpsrv/host.cc
index abd8113395..573047bdaf 100644
--- a/src/lib/dhcpsrv/host.cc
+++ b/src/lib/dhcpsrv/host.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,9 +9,13 @@
#include <dhcpsrv/host.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
+#include <asiolink/io_address.h>
#include <exceptions/exceptions.h>
#include <sstream>
+using namespace isc::data;
+using namespace isc::asiolink;
+
namespace isc {
namespace dhcp {
@@ -160,7 +164,8 @@ Host::getIdentifierType(const std::string& identifier_name) {
} else if (identifier_name == "client-id") {
return (IDENT_CLIENT_ID);
-
+ } else if (identifier_name == "flex-id") {
+ return (IDENT_FLEX);
} else {
isc_throw(isc::BadValue, "invalid client identifier type '"
<< identifier_name << "'");
@@ -204,6 +209,9 @@ Host::getIdentifierAsText(const IdentifierType& type, const uint8_t* value,
case IDENT_CLIENT_ID:
s << "client-id";
break;
+ case IDENT_FLEX:
+ s << "flex-id";
+ break;
default:
// This should never happen actually, unless we add new identifier
// and forget to add a case for it above.
@@ -229,6 +237,9 @@ Host::getIdentifierName(const IdentifierType& type) {
case Host::IDENT_CLIENT_ID:
return ("client-id");
+ case Host::IDENT_FLEX:
+ return ("flex-id");
+
default:
;
}
@@ -257,7 +268,7 @@ Host::setIdentifier(const std::string& identifier, const std::string& name) {
// Set identifier type.
identifier_type_ = getIdentifierType(name);
- // Idetifier value can either be specified as string of hexadecimal
+ // Identifier value can either be specified as string of hexadecimal
// digits or a string in quotes. The latter is copied to a vector excluding
// quote characters.
@@ -393,6 +404,122 @@ Host::setBootFileName(const std::string& boot_file_name) {
boot_file_name_ = boot_file_name;
}
+ElementPtr
+Host::toElement4() const {
+
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+ // Set the identifier
+ Host::IdentifierType id_type = getIdentifierType();
+ if (id_type == Host::IDENT_HWADDR) {
+ HWAddrPtr hwaddr = getHWAddress();
+ map->set("hw-address", Element::create(hwaddr->toText(false)));
+ } else if (id_type == Host::IDENT_DUID) {
+ DuidPtr duid = getDuid();
+ map->set("duid", Element::create(duid->toText()));
+ } else if (id_type == Host::IDENT_CIRCUIT_ID) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string circuit_id = util::encode::encodeHex(bin);
+ map->set("circuit-id", Element::create(circuit_id));
+ } else if (id_type == Host::IDENT_CLIENT_ID) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string client_id = util::encode::encodeHex(bin);
+ map->set("client-id", Element::create(client_id));
+ } else if (id_type == Host::IDENT_FLEX) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string flex = util::encode::encodeHex(bin);
+ map->set("flex-id", Element::create(flex));
+ } else {
+ isc_throw(ToElementError, "invalid identifier type: " << id_type);
+ }
+ // Set the reservation
+ const IOAddress& address = getIPv4Reservation();
+ map->set("ip-address", Element::create(address.toText()));
+ // Set the hostname
+ const std::string& hostname = getHostname();
+ map->set("hostname", Element::create(hostname));
+ // Set next-server
+ const IOAddress& next_server = getNextServer();
+ map->set("next-server", Element::create(next_server.toText()));
+ // Set server-hostname
+ const std::string& server_hostname = getServerHostname();
+ map->set("server-hostname", Element::create(server_hostname));
+ // Set boot-file-name
+ const std::string& boot_file_name = getBootFileName();
+ map->set("boot-file-name", Element::create(boot_file_name));
+ // Set client-classes
+ const ClientClasses& cclasses = getClientClasses4();
+ ElementPtr classes = Element::createList();
+ for (ClientClasses::const_iterator cclass = cclasses.cbegin();
+ cclass != cclasses.end(); ++cclass) {
+ classes->add(Element::create(*cclass));
+ }
+ map->set("client-classes", classes);
+ // Set option-data
+ ConstCfgOptionPtr opts = getCfgOption4();
+ map->set("option-data", opts->toElement());
+
+ return (map);
+}
+
+ElementPtr
+Host::toElement6() const {
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+ // Set the identifier
+ Host::IdentifierType id_type = getIdentifierType();
+ if (id_type == Host::IDENT_HWADDR) {
+ HWAddrPtr hwaddr = getHWAddress();
+ map->set("hw-address", Element::create(hwaddr->toText(false)));
+ } else if (id_type == Host::IDENT_DUID) {
+ DuidPtr duid = getDuid();
+ map->set("duid", Element::create(duid->toText()));
+ } else if (id_type == Host::IDENT_CIRCUIT_ID) {
+ isc_throw(ToElementError, "unexpected circuit-id DUID type");
+ } else if (id_type == Host::IDENT_CLIENT_ID) {
+ isc_throw(ToElementError, "unexpected client-id DUID type");
+ } else if (id_type == Host::IDENT_FLEX) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string flex = util::encode::encodeHex(bin);
+ map->set("flex-id", Element::create(flex));
+ } else {
+ isc_throw(ToElementError, "invalid DUID type: " << id_type);
+ }
+ // Set reservations (ip-addresses)
+ IPv6ResrvRange na_resv = getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ElementPtr resvs = Element::createList();
+ for (IPv6ResrvIterator resv = na_resv.first;
+ resv != na_resv.second; ++resv) {
+ resvs->add(Element::create(resv->second.toText()));
+ }
+ map->set("ip-addresses", resvs);
+ // Set reservations (prefixes)
+ IPv6ResrvRange pd_resv = getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ resvs = Element::createList();
+ for (IPv6ResrvIterator resv = pd_resv.first;
+ resv != pd_resv.second; ++resv) {
+ resvs->add(Element::create(resv->second.toText()));
+ }
+ map->set("prefixes", resvs);
+ // Set the hostname
+ const std::string& hostname = getHostname();
+ map->set("hostname", Element::create(hostname));
+ // Set client-classes
+ const ClientClasses& cclasses = getClientClasses6();
+ ElementPtr classes = Element::createList();
+ for (ClientClasses::const_iterator cclass = cclasses.cbegin();
+ cclass != cclasses.end(); ++cclass) {
+ classes->add(Element::create(*cclass));
+ }
+ map->set("client-classes", classes);
+
+ // Set option-data
+ ConstCfgOptionPtr opts = getCfgOption6();
+ map->set("option-data", opts->toElement());
+
+ return (map);
+}
+
std::string
Host::toText() const {
std::ostringstream s;
diff --git a/src/lib/dhcpsrv/host.h b/src/lib/dhcpsrv/host.h
index fa080e67ce..1b3552429b 100644
--- a/src/lib/dhcpsrv/host.h
+++ b/src/lib/dhcpsrv/host.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#define HOST_H
#include <asiolink/io_address.h>
+#include <cc/data.h>
#include <dhcp/classify.h>
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
@@ -184,12 +185,13 @@ public:
IDENT_HWADDR,
IDENT_DUID,
IDENT_CIRCUIT_ID,
- IDENT_CLIENT_ID
+ IDENT_CLIENT_ID,
+ IDENT_FLEX, ///< Flexible host identifier.
};
/// @brief Constant pointing to the last identifier of the
/// @ref IdentifierType enumeration.
- static const IdentifierType LAST_IDENTIFIER_TYPE = IDENT_CLIENT_ID;
+ static const IdentifierType LAST_IDENTIFIER_TYPE = IDENT_FLEX;
/// @brief Constructor.
///
@@ -240,7 +242,7 @@ public:
/// - "yy:yy:yy:yy:yy:yy"
/// - "yyyyyyyyyy",
/// - "0xyyyyyyyyyy",
- /// - "'some identfier'".
+ /// - "'some identifier'".
/// where y is a hexadecimal digit.
///
/// Note that it is possible to use textual representation, e.g. 'some identifier',
@@ -501,7 +503,7 @@ public:
/// @brief Returns pointer to the DHCPv4 option data configuration for
/// this host.
///
- /// Returned pointer can be used to add, remove and udate options
+ /// Returned pointer can be used to add, remove and update options
/// reserved for a host.
CfgOptionPtr getCfgOption4() {
return (cfg_option4_);
@@ -516,7 +518,7 @@ public:
/// @brief Returns pointer to the DHCPv6 option data configuration for
/// this host.
///
- /// Returned pointer can be used to add, remove and udate options
+ /// Returned pointer can be used to add, remove and update options
/// reserved for a host.
CfgOptionPtr getCfgOption6() {
return (cfg_option6_);
@@ -543,6 +545,16 @@ public:
return (host_id_);
}
+ /// @brief Unparses (converts to Element representation) IPv4 host
+ ///
+ /// @return Element representation of the host
+ isc::data::ElementPtr toElement4() const;
+
+ /// @brief Unparses (converts to Element representation) IPv4 host
+ ///
+ /// @return Element representation of the host
+ isc::data::ElementPtr toElement6() const;
+
private:
/// @brief Adds new client class for DHCPv4 or DHCPv6.
diff --git a/src/lib/dhcpsrv/host_container.h b/src/lib/dhcpsrv/host_container.h
index 5d9f49e2f9..e352051f36 100644
--- a/src/lib/dhcpsrv/host_container.h
+++ b/src/lib/dhcpsrv/host_container.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -64,7 +64,7 @@ typedef boost::multi_index_container<
// Second index is used to search for the host using reserved IPv4
// address.
boost::multi_index::ordered_non_unique<
- // Index using values returned by the @c Host::getIPv4Resrvation.
+ // Index using values returned by the @c Host::getIPv4Reservation.
boost::multi_index::const_mem_fun<Host, const asiolink::IOAddress&,
&Host::getIPv4Reservation>
>
diff --git a/src/lib/dhcpsrv/host_data_source_factory.cc b/src/lib/dhcpsrv/host_data_source_factory.cc
index 380d6c8a3b..d19d63dc14 100644
--- a/src/lib/dhcpsrv/host_data_source_factory.cc
+++ b/src/lib/dhcpsrv/host_data_source_factory.cc
@@ -47,7 +47,7 @@ HostDataSourceFactory::create(const std::string& dbaccess) {
DatabaseConnection::ParameterMap parameters =
DatabaseConnection::parse(dbaccess);
- // Get the databaase type and open the corresponding database
+ // Get the database type and open the corresponding database
DatabaseConnection::ParameterMap::iterator it = parameters.find("type");
if (it == parameters.end()) {
isc_throw(InvalidParameter, "Host database configuration does not "
diff --git a/src/lib/dhcpsrv/host_mgr.cc b/src/lib/dhcpsrv/host_mgr.cc
index 090a5433ac..302da6c0d7 100644
--- a/src/lib/dhcpsrv/host_mgr.cc
+++ b/src/lib/dhcpsrv/host_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -131,9 +131,33 @@ HostMgr::get4(const SubnetID& subnet_id,
ConstHostPtr host = getCfgHosts()->get4(subnet_id, identifier_type,
identifier_begin, identifier_len);
if (!host && alternate_source_) {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+ HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
+ identifier_len));
+
host = alternate_source_->get4(subnet_id, identifier_type,
identifier_begin, identifier_len);
+
+ if (host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_HOST)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
+ identifier_len))
+ .arg(host->toText());
+
+ } else {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_NULL)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
+ identifier_len));
+ }
}
+
return (host);
}
@@ -193,8 +217,33 @@ HostMgr::get6(const SubnetID& subnet_id,
ConstHostPtr host = getCfgHosts()->get6(subnet_id, identifier_type,
identifier_begin, identifier_len);
if (!host && alternate_source_) {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+ HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
+ identifier_len));
+
+
host = alternate_source_->get6(subnet_id, identifier_type,
identifier_begin, identifier_len);
+
+ if (host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
+ identifier_len))
+ .arg(host->toText());
+
+ } else {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
+ identifier_len));
+ }
+
}
return (host);
}
@@ -216,11 +265,45 @@ HostMgr::get6(const SubnetID& subnet_id,
void
HostMgr::add(const HostPtr& host) {
if (!alternate_source_) {
- isc_throw(NoHostDataSourceManager, "unable to add new host because there is "
- "no alternate host data source present");
+ isc_throw(NoHostDataSourceManager, "Unable to add new host because there is "
+ "no hosts-database configured.");
}
alternate_source_->add(host);
}
+bool
+HostMgr::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
+ if (!alternate_source_) {
+ isc_throw(NoHostDataSourceManager, "Unable to delete a host because there is "
+ "no hosts-database configured.");
+ }
+
+ return (alternate_source_->del(subnet_id, addr));
+}
+
+bool
+HostMgr::del4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) {
+ if (!alternate_source_) {
+ isc_throw(NoHostDataSourceManager, "Unable to delete a host because there is "
+ "no hosts-database configured.");
+ }
+
+ return (alternate_source_->del4(subnet_id, identifier_type,
+ identifier_begin, identifier_len));
+}
+
+bool
+HostMgr::del6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) {
+ if (!alternate_source_) {
+ isc_throw(NoHostDataSourceManager, "unable to delete a host because there is "
+ "no alternate host data source present");
+ }
+
+ return (alternate_source_->del6(subnet_id, identifier_type,
+ identifier_begin, identifier_len));
+}
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h
index 128b311f4b..dd5177a3dd 100644
--- a/src/lib/dhcpsrv/host_mgr.h
+++ b/src/lib/dhcpsrv/host_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -117,7 +117,7 @@ public:
/// reservations from the alternate source.
///
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -162,11 +162,11 @@ public:
/// @brief Returns a host connected to the IPv4 subnet.
///
/// This method returns a single reservation for a particular host as
- /// documneted in the @c BaseHostDataSource::get4.
+ /// documented in the @c BaseHostDataSource::get4.
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -213,7 +213,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -237,7 +237,7 @@ public:
/// @brief Returns a host from specific subnet and reserved address.
///
- /// @param subnet_id subnet identfier.
+ /// @param subnet_id subnet identifier.
/// @param addr specified address.
///
/// @return Const @c host object that has a reservation for specified address.
@@ -269,6 +269,59 @@ public:
return (alternate_source_);
}
+ /// @brief Sets the alternate host data source.
+ ///
+ /// Note: This should be used only for testing. Do not use
+ /// in production. Normal control flow assumes that
+ /// HostMgr::create(...) is called and it instantiates
+ /// appropriate host data source. However, some tests
+ /// (e.g. host_cmds) implement their own very simple
+ /// data source. It's not production ready by any means,
+ /// so it does not belong in host_data_source_factory.cc.
+ /// The testing nature of this method is reflected in its name.
+ ///
+ /// @param source new source to be set (may be NULL)
+ void setTestHostDataSource(const HostDataSourcePtr& source) {
+ alternate_source_ = source;
+ }
+
+ /// @brief Attempts to delete a host by address.
+ ///
+ /// This method supports both v4 and v6.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false otherwise.
+ virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr);
+
+ /// @brief Attempts to delete a host by (subnet4-id, identifier, identifier-type)
+ ///
+ /// This method supports v4 only.
+ ///
+ /// @param subnet_id IPv4 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false otherwise.
+ virtual bool
+ del4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Attempts to delete a host by (subnet6-id, identifier, identifier-type)
+ ///
+ /// This method supports v6 only.
+ ///
+ /// @param subnet_id IPv6 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false otherwise.
+ virtual bool
+ del6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
private:
/// @brief Private default constructor.
diff --git a/src/lib/dhcpsrv/hosts_log.h b/src/lib/dhcpsrv/hosts_log.h
index 45fa64b44a..e4b1faf62d 100644
--- a/src/lib/dhcpsrv/hosts_log.h
+++ b/src/lib/dhcpsrv/hosts_log.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -24,23 +24,23 @@ namespace dhcp {
///
/// An example of the normal operation is the call to one of the functions
/// which retrieve the reservations or add new reservation.
-const int HOSTS_DBG_TRACE = DBGLVL_TRACE_BASIC;
+const int HOSTS_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
/// @brief Records the results of the lookups
///
/// Messages logged at this level will typically contain summary of the
/// data retrieved.
-const int HOSTS_DBG_RESULTS = DBGLVL_TRACE_BASIC_DATA;
+const int HOSTS_DBG_RESULTS = isc::log::DBGLVL_TRACE_BASIC_DATA;
/// @brief Record detailed traces
///
/// Messages logged at this level will log detailed tracing information.
-const int HOSTS_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
+const int HOSTS_DBG_TRACE_DETAIL = isc::log::DBGLVL_TRACE_DETAIL;
/// @brief Records detailed results of lookups.
///
/// Messages logged at this level will contain detailed results.
-const int HOSTS_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+const int HOSTS_DBG_TRACE_DETAIL_DATA = isc::log::DBGLVL_TRACE_DETAIL_DATA;
///@}
diff --git a/src/lib/dhcpsrv/hosts_messages.mes b/src/lib/dhcpsrv/hosts_messages.mes
index 0e08b97af1..00f0b23b4b 100644
--- a/src/lib/dhcpsrv/hosts_messages.mes
+++ b/src/lib/dhcpsrv/hosts_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -91,7 +91,7 @@ specific prefix/prefix length. The arguments specify prefix, prefix
length and host details respectively.
% HOSTS_CFG_GET_ONE_PREFIX_NULL host not found using prefix %1/%2
-This debug messsage is issued when no host was found for a specified
+This debug message is issued when no host was found for a specified
prefix and prefix length.
% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4 get one host with reservation for subnet id %1 and IPv4 address %2
@@ -146,6 +146,20 @@ host connected to the specific subnet and identified by the HW address
or DUID, and it is starting to search for this host in the alternate
host data source.
+% HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER get one host with IPv4 reservation for subnet id %1, identified by %2
+This debug message is issued when starting to retrieve a host holding
+IPv4 reservation, which is connected to a specific subnet and
+is identified by a specific unique identifier.
+
+% HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_HOST using subnet id %1 and identifier %2, found host: %3
+This debug message includes the details of a host returned by an
+alternate hosts data source using a subnet id and specific host
+identifier.
+
+% HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_NULL host not found using subnet id %1 and identifier %2
+This debug message is issued when no host was found using the specified
+subnet id and host identifier.
+
% HOSTS_MGR_ALTERNATE_GET6_PREFIX trying alternate source for host using prefix %1/%2
This debug message is issued when the Host Manager doesn't find the
host connected to the specific subnet and having the reservation for
@@ -163,3 +177,17 @@ This debug message is issued when the Host Manager doesn't find the
host connected to the specific subnet and identified by the specified
DUID or HW Address, and it is starting to search for this host in the
alternate host data source.
+
+% HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER get one host with IPv6 reservation for subnet id %1, identified by %2
+This debug message is issued when starting to retrieve a host holding
+IPv4 reservation, which is connected to a specific subnet and
+is identified by a specific unique identifier.
+
+% HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST using subnet id %1 and identifier %2, found host: %3
+This debug message includes the details of a host returned by an
+alternate hosts data source using a subnet id and specific host
+identifier.
+
+% HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL host not found using subnet id %1 and identifier %2
+This debug message is issued when no host was found using the specified
+subnet id and host identifier.
diff --git a/src/lib/dhcpsrv/lease.cc b/src/lib/dhcpsrv/lease.cc
index 7dceb9db69..1aa6e28b70 100644
--- a/src/lib/dhcpsrv/lease.cc
+++ b/src/lib/dhcpsrv/lease.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
#include <iostream>
using namespace isc::util;
+using namespace isc::data;
using namespace std;
namespace isc {
@@ -220,6 +221,30 @@ Lease4::operator=(const Lease4& other) {
return (*this);
}
+isc::data::ElementPtr
+Lease4::toElement() const {
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+ map->set("ip-address", Element::create(addr_.toText()));
+ map->set("subnet-id", Element::create(static_cast<long int>(subnet_id_)));
+ map->set("hw-address", Element::create(hwaddr_->toText(false)));
+
+ if (client_id_) {
+ map->set("client-id", Element::create(client_id_->toText()));
+ }
+
+ map->set("cltt", Element::create(cltt_));
+ map->set("valid-lft", Element::create(static_cast<long int>(valid_lft_)));
+
+ map->set("fqdn-fwd", Element::create(fqdn_fwd_));
+ map->set("fqdn-rev", Element::create(fqdn_rev_));
+ map->set("hostname", Element::create(hostname_));
+
+ map->set("state", Element::create(static_cast<int>(state_)));
+
+ return (map);
+}
+
Lease6::Lease6(Lease::Type type, const isc::asiolink::IOAddress& addr,
DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid,
uint32_t t1, uint32_t t2, SubnetID subnet_id,
@@ -362,6 +387,37 @@ Lease6::operator==(const Lease6& other) const {
state_ == other.state_);
}
+isc::data::ElementPtr
+Lease6::toElement() const {
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+ map->set("ip-address", Element::create(addr_.toText()));
+ map->set("type", Element::create(typeToText(type_)));
+ if (type_ == Lease::TYPE_PD) {
+ map->set("prefix-len", Element::create(prefixlen_));
+ }
+ map->set("iaid", Element::create(static_cast<long int>(iaid_)));
+ map->set("duid", Element::create(duid_->toText()));
+ map->set("subnet-id", Element::create(static_cast<long int>(subnet_id_)));
+
+ map->set("cltt", Element::create(cltt_));
+ map->set("preferred-lft", Element::create(static_cast<long int>(preferred_lft_)));
+ map->set("valid-lft", Element::create(static_cast<long int>(valid_lft_)));
+
+ map->set("fqdn-fwd", Element::create(fqdn_fwd_));
+ map->set("fqdn-rev", Element::create(fqdn_rev_));
+ map->set("hostname", Element::create(hostname_));
+
+ if (hwaddr_) {
+ map->set("hw-address", Element::create(hwaddr_->toText(false)));
+ }
+
+ map->set("state", Element::create(static_cast<long int>(state_)));
+
+ return (map);
+}
+
+
std::ostream&
operator<<(std::ostream& os, const Lease& lease) {
os << lease.toText();
diff --git a/src/lib/dhcpsrv/lease.h b/src/lib/dhcpsrv/lease.h
index 93cb7884cd..7fabf6720d 100644
--- a/src/lib/dhcpsrv/lease.h
+++ b/src/lib/dhcpsrv/lease.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,6 +11,7 @@
#include <dhcp/duid.h>
#include <dhcp/option.h>
#include <dhcp/hwaddr.h>
+#include <cc/cfg_to_element.h>
namespace isc {
namespace dhcp {
@@ -25,7 +26,7 @@ typedef uint32_t SubnetID;
///
/// This structure holds all information that is common between IPv4 and IPv6
/// leases.
-struct Lease {
+struct Lease : public isc::data::CfgToElement {
/// @brief Type of lease or pool
typedef enum {
@@ -37,7 +38,7 @@ struct Lease {
/// @brief returns text representation of a lease type
/// @param type lease or pool type to be converted
- /// @return text decription
+ /// @return text description
static std::string typeToText(Type type);
/// @name Common lease states constants.
@@ -144,7 +145,7 @@ struct Lease {
/// @brief Holds the lease state(s).
///
/// This is the field that holds the lease state(s). Typically, a
- /// lease remains in a single states. However, it is posible to
+ /// lease remains in a single states. However, it is possible to
/// define a value for state which indicates that the lease remains
/// in multiple logical states.
///
@@ -390,7 +391,7 @@ struct Lease4 : public Lease {
/// @brief Convert lease to printable form
///
- /// @return Textual represenation of lease data
+ /// @return Textual representation of lease data
virtual std::string toText() const;
/// @brief Sets IPv4 lease to declined state.
@@ -400,6 +401,9 @@ struct Lease4 : public Lease {
/// @param probation_period valid lifetime will be set to this value
void decline(uint32_t probation_period);
+ /// @brief Return the JSON representation of a lease
+ virtual isc::data::ElementPtr toElement() const;
+
/// @todo: Add DHCPv4 failover related fields here
};
@@ -532,6 +536,9 @@ struct Lease6 : public Lease {
///
/// @return String form of the lease
virtual std::string toText() const;
+
+ /// @brief Return the JSON representation of a lease
+ virtual isc::data::ElementPtr toElement() const;
};
/// @brief Pointer to a Lease6 structure.
@@ -548,7 +555,7 @@ typedef std::vector<Lease6Ptr> Lease6Collection;
/// Dumps the output of Lease::toText to the given stream.
/// @param os output stream to which the output is
/// @param lease reference to Lease object to dump
-/// @return a reference to the output stream paramater
+/// @return a reference to the output stream parameter
std::ostream&
operator<<(std::ostream& os, const Lease& lease);
diff --git a/src/lib/dhcpsrv/lease_file_loader.h b/src/lib/dhcpsrv/lease_file_loader.h
index 77add53311..cc9385c06a 100644
--- a/src/lib/dhcpsrv/lease_file_loader.h
+++ b/src/lib/dhcpsrv/lease_file_loader.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -218,7 +218,7 @@ public:
}
};
-} // namesapce dhcp
-} // namespace isc
+} // namespace dhcp
+} // namespace isc
#endif // LEASE_FILE_LOADER_H
diff --git a/src/lib/dhcpsrv/lease_file_stats.h b/src/lib/dhcpsrv/lease_file_stats.h
index 48894a6b8a..6a46480fac 100644
--- a/src/lib/dhcpsrv/lease_file_stats.h
+++ b/src/lib/dhcpsrv/lease_file_stats.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -53,7 +53,7 @@ public:
return (write_leases_);
}
- /// @brief Gets the number of errors when writting leases
+ /// @brief Gets the number of errors when writing leases
uint32_t getWriteErrs() const {
return (write_errs_);
}
@@ -88,7 +88,7 @@ protected:
uint32_t write_errs_;
};
-} // namespace isc::dhcp
-} // namesapce isc
+} // namespace dhcp
+} // namespace isc
#endif // LEASE_FILE_STATS_H
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc
index e57348655a..205e565985 100644
--- a/src/lib/dhcpsrv/lease_mgr.cc
+++ b/src/lib/dhcpsrv/lease_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -62,7 +62,8 @@ LeaseMgr::recountLeaseStats4() {
// Zero out the global stats.
int64_t zero = 0;
stats_mgr.setValue("declined-addresses", zero);
- stats_mgr.setValue("declined-reclaimed-addresses", zero);
+ stats_mgr.setValue("reclaimed-declined-addresses", zero);
+ stats_mgr.setValue("reclaimed-leases", zero);
// Clear subnet level stats. This ensures we don't end up with corner
// cases that leave stale values in place.
@@ -79,8 +80,13 @@ LeaseMgr::recountLeaseStats4() {
stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
"declined-addresses"),
zero);
+
+ stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"),
+ zero);
+
stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
- "declined-reclaimed-addresses"),
+ "reclaimed-leases"),
zero);
}
@@ -133,7 +139,8 @@ LeaseMgr::recountLeaseStats6() {
// clearing it when we clear the rest.
int64_t zero = 0;
stats_mgr.setValue("declined-addresses", zero);
- stats_mgr.setValue("declined-reclaimed-addresses", zero);
+ stats_mgr.setValue("reclaimed-declined-addresses", zero);
+ stats_mgr.setValue("reclaimed-leases", zero);
// Clear subnet level stats. This ensures we don't end up with corner
// cases that leave stale values in place.
@@ -153,12 +160,16 @@ LeaseMgr::recountLeaseStats6() {
stats_mgr.setValue(StatsMgr::
generateName("subnet", subnet_id,
- "declined-reclaimed-addresses"),
+ "reclaimed-declined-addresses"),
zero);
stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
"assigned-pds"),
zero);
+
+ stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"),
+ zero);
}
// Get counts per state per subnet. Iterate over the result set
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index a07cd47648..5f70a55c13 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -51,7 +51,7 @@
/// However, there is another approach that can be reliably used to provide
/// failover, even without the actual failover protocol implemented. As the
/// first backend will use MySQL, we will be able to use Multi-Master capability
-/// offered by MySQL and use two separatate Kea instances connecting to the
+/// offered by MySQL and use two separate Kea instances connecting to the
/// same database.
///
/// Nevertheless, we hope to have failover protocol eventually implemented in
@@ -495,7 +495,7 @@ public:
/// a lease state, and the number of leases of that type, in that state
/// and is ordered by subnet id. The method iterates over the
/// result set rows, setting the appropriate statistic per subnet and
- /// adding to the approporate global statistic.
+ /// adding to the appropriate global statistic.
void recountLeaseStats4();
/// @brief Virtual method which creates and runs the IPv4 lease stats query
@@ -526,7 +526,7 @@ public:
/// a subnet id, a lease type, a lease state, and the number of leases
/// of that type, in that state and is ordered by subnet id. The method
/// iterates over the result set rows, setting the appropriate statistic
- /// per subnet and adding to the approporate global statistic.
+ /// per subnet and adding to the appropriate global statistic.
void recountLeaseStats6();
/// @brief Virtual method which creates and runs the IPv6 lease stats query
@@ -539,6 +539,24 @@ public:
/// @return A populated LeaseStatsQuery
virtual LeaseStatsQueryPtr startLeaseStatsQuery6();
+ /// @brief Virtual method which removes specified leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @param subnet_id identifier of the subnet (or 0 for all subnets)
+ /// @return number of leases removed.
+ virtual size_t wipeLeases4(const SubnetID& subnet_id) = 0;
+
+ /// @brief Virtual method which removes specified leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @param subnet_id identifier of the subnet (or 0 for all subnets)
+ /// @return number of leases removed.
+ virtual size_t wipeLeases6(const SubnetID& subnet_id) = 0;
+
/// @brief Return backend type
///
/// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
@@ -588,10 +606,6 @@ public:
/// Rolls back all pending database operations. On databases that don't
/// support transactions, this is a no-op.
virtual void rollback() = 0;
-
- /// @todo: Add host management here
- /// As host reservation is outside of scope for 2012, support for hosts
- /// is currently postponed.
};
}; // end of isc::dhcp namespace
diff --git a/src/lib/dhcpsrv/libdhcpsrv.dox b/src/lib/dhcpsrv/libdhcpsrv.dox
index 71b5c6cc39..a8dc6cca70 100644
--- a/src/lib/dhcpsrv/libdhcpsrv.dox
+++ b/src/lib/dhcpsrv/libdhcpsrv.dox
@@ -1,11 +1,11 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/**
- @page libdhcpsrv libdhcpsrv - Server DHCP library
+ @page libdhcpsrv libkea-dhcpsrv - Server DHCP Library
This library contains code used for the DHCPv4 and DHCPv6 servers' operations,
including the "Lease Manager" that manages information about leases and the
@@ -79,6 +79,15 @@ configuration. For example: the value of 1 identifies an immediate
predecessor of the current configuration, the value of 2 identifies the
one that occurred before it etc.
+All configuration classes are derived from the abstract base class
+\ref isc::data::CfgToElement and define the toElement virtual method
+which returns a \ref isc::data::ConstElementPtr which must be
+parsed into the same object, i.e. fulfill this property:
+@code
+for all valid C: parse(parse(C)->toElement()) == parse(C)
+@endcode
+
+
@section hostmgr Host Manager
Host Manager implemented by the \ref isc::dhcp::HostMgr is a singleton object
@@ -263,54 +272,28 @@ the server.
@section timerManager Timer Manager
-The fundamental role of the DHCP server is to receive and process DHCP
-messages received over the sockets opened on network interfaces. The
-servers' include the main loops in which the servers passively wait
-for the messages. This is done by calling the
-@c isc::dhcp::IfaceMgr::receive4 and/or @c isc::dhcp::IfaceMgr::receive6
-methods for DHCPv4 and DHCPv6 server respectively. Internally, these
-methods call @c select() on open sockets, which blocks for a
-specified amount of time.
-
-The implication of using the @c select() is that the server has no
-means to process any "events" while it is waiting for the @c select()
-to return. An example of such an event is the expiration of the timer
-which controls when the server should detect and process expired
-leases.
-
-The @c isc::dhcp::TimerMgr has been created to address the issue of
-processing expired leases according to the the dedicated timer.
-Nevertheless, this concept is universal and should be used for
-all timers which need to be triggered asynchronously, i.e. independently
-from processing the DHCP messages.
-
-The @c TimerMgr allows for registering timers and associating them with
-user callback functions, which are executed without waiting for the
-call to the @c select() function to return as a result of the timeout.
-When the particular timer elapses, the blocking call to select is
-interrupted by sending data over a dedicated (for a timer)
-@c isc::util::WatchSocket. Each timer has an instance of
-@c isc::util::WatchSocket associated with it, and each such socket
-is registered with the @c IfaceMgr using the @c IfaceMgr::addExternalSocket.
-When the transmission of the data over the watch socket interrupts the
-@c select() call, the user callback is executed by
-@c isc::dhcp::IfaceMgr and the watch socket is cleared to accept
-subsequent events for that particular timer.
-
-The timers are implemented using the @c isc::asiolink::IntervalTimer class.
-They are run in a dedicated thread which is owned (created and destroyed)
-by @c isc::dhcp::TimerMgr. This worker thread runs an instance
-of @c isc::asiolink::IOService object which is associated with all
-registered timers. The thread uses a common callback function which
-is executed when a timer elapses. This callback function receives
-a name of the elapsed timer as an argument and, based on that, selects the
-appropriate @c isc::util::WatchSocket to be marked as ready. In order to
-overcome the race conditions with the main thread, the worker thread blocks
-right after it marks the watch socket as ready, and waits for this
-socket to be cleared by the main thread. This is the indication
-that the timer specific callback function has been invoked and the
-worker thread may continue monitoring registered timers and signal
-their readiness when they elapse.
+The @c isc::dhcp::TimerMgr is a singleton class used throughout the
+server process to register and unregister timers triggering periodic
+tasks such as lease file cleanup, reclamation of expired leases etc.
+
+The Timer Manger is using ASIO deadline timers (wrapped in
+@c isc::asiolink::IntervalTimer class) to execute tasks according to
+the configured periods. Therefore, the server process must provide the
+Timer Manager with the pointer to the @c isc::asiolink::IOService which
+the server is using to run asynchronous tasks.
+
+Current implementation of the DHCP servers uses synchronous calls to
+@c select() function to check if any transmission has been received
+on any socket. This poses a problem with running asynchronous calls
+via @c IOService in the main server loop because the @c select()
+blocks for a specified amount of time while asynchronous calls
+are not triggered. In the future we should migrate from the synchronous
+@c select() calls into asynchronous calls using ASIO. Currently,
+we mitigate the problem by lowering the @c select() timeout to 1s,
+and polling @c IOService for "ready" timers (handlers) after
+@c select() returns. This may cause delays of "ready" handlers
+execution by around 1s. However, this is acceptable for the current
+applications of the periodic timers.
@section leaseReclamationRoutine Leases Reclamation Routine
@@ -476,4 +459,5 @@ is used by DHCPv4 and DHCPv6 components.
DHCPv4-over-DHCPv6 which are relayed by a DHCPv6 relay are not yet supported.
+
*/
diff --git a/src/lib/dhcpsrv/logging_info.cc b/src/lib/dhcpsrv/logging_info.cc
index ebccdf7f2f..9cedf6c4ca 100644
--- a/src/lib/dhcpsrv/logging_info.cc
+++ b/src/lib/dhcpsrv/logging_info.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
#include <log/logger_name.h>
using namespace isc::log;
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -22,6 +23,22 @@ LoggingDestination::equals(const LoggingDestination& other) const {
flush_ == other.flush_);
}
+ElementPtr
+LoggingDestination::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ // Set output
+ result->set("output", Element::create(output_));
+ // Set maxver
+ result->set("maxver", Element::create(maxver_));
+ // Set maxsize
+ result->set("maxsize", Element::create(static_cast<long long>(maxsize_)));
+ // Set flush
+ result->set("flush", Element::create(flush_));
+
+ return(result);
+}
+
LoggingInfo::LoggingInfo()
: name_("kea"), severity_(isc::log::INFO), debuglevel_(0) {
// If configuration Manager is in the verbose mode, we need to modify the
@@ -89,7 +106,7 @@ LoggingInfo::toSpec() const {
LoggerSpecification spec(name_, severity_, debuglevel_);
- // Go over logger destinations and create output options accordinly.
+ // Go over logger destinations and create output options accordingly.
for (std::vector<LoggingDestination>::const_iterator dest =
destinations_.begin(); dest != destinations_.end(); ++dest) {
@@ -125,6 +142,8 @@ LoggingInfo::toSpec() const {
// Not a recognized destination, assume a file.
option.destination = OutputOption::DEST_FILE;
option.filename = dest->output_;
+ option.maxsize = dest->maxsize_;
+ option.maxver = dest->maxver_;
}
// Copy the immediate flush flag
@@ -137,5 +156,49 @@ LoggingInfo::toSpec() const {
return (spec);
}
+ElementPtr
+LoggingInfo::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set name
+ result->set("name", Element::create(name_));
+ // Set output_options
+ ElementPtr options = Element::createList();
+ for (std::vector<LoggingDestination>::const_iterator dest =
+ destinations_.cbegin();
+ dest != destinations_.cend(); ++dest) {
+ options->add(dest->toElement());
+ }
+ result->set("output_options", options);
+ // Set severity
+ std::string severity;
+ switch (severity_) {
+ case isc::log::DEBUG:
+ severity = "DEBUG";
+ break;
+ case isc::log::INFO:
+ severity = "INFO";
+ break;
+ case isc::log::WARN:
+ severity = "WARN";
+ break;
+ case isc::log::ERROR:
+ severity = "ERROR";
+ break;
+ case isc::log::FATAL:
+ severity = "FATAL";
+ break;
+ case isc::log::NONE:
+ severity = "NONE";
+ break;
+ default:
+ isc_throw(ToElementError, "illegal severity: " << severity_);
+ break;
+ }
+ result->set("severity", Element::create(severity));
+ // Set debug level
+ result->set("debuglevel", Element::create(debuglevel_));
+ return (result);
+}
+
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/logging_info.h b/src/lib/dhcpsrv/logging_info.h
index ac5ec01d99..5dd5a11257 100644
--- a/src/lib/dhcpsrv/logging_info.h
+++ b/src/lib/dhcpsrv/logging_info.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
#include <log/logger_level.h>
#include <log/logger_specification.h>
+#include <cc/cfg_to_element.h>
#include <stdint.h>
#include <vector>
@@ -18,7 +19,7 @@ namespace dhcp {
/// @brief Defines single logging destination
///
/// This structure is used to keep log4cplus configuration parameters.
-struct LoggingDestination {
+struct LoggingDestination : public isc::data::CfgToElement {
/// @brief defines logging destination output
///
@@ -44,8 +45,13 @@ struct LoggingDestination {
/// @brief Default constructor.
LoggingDestination()
- : output_("stdout"), maxver_(1), maxsize_(204800), flush_(true) {
+ : output_("stdout"), maxver_(1), maxsize_(10240000), flush_(true) {
}
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
};
/// @brief structure that describes one logging entry
@@ -65,7 +71,7 @@ struct LoggingDestination {
/// "severity": "WARN",
/// "debuglevel": 99
/// },
-struct LoggingInfo {
+struct LoggingInfo : public isc::data::CfgToElement {
/// @brief logging name
std::string name_;
@@ -116,6 +122,11 @@ struct LoggingInfo {
/// @brief Converts logger configuration to a spec.
isc::log::LoggerSpecification toSpec() const;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
};
/// @brief storage for logging information in log4cplus format
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index e24b51eb2c..8f2267906a 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -50,7 +50,7 @@ namespace dhcp {
/// the interval timer and assigns a callback function (pointer to which is
/// passed in the constructor), which will be called at the specified
/// intervals to perform the cleanup. It is also responsible for creating
-/// and maintaing the object which is used to spawn the new process which
+/// and maintaining the object which is used to spawn the new process which
/// executes the @c kea-lfc program.
///
/// This functionality is enclosed in a separate class so as the implementation
@@ -127,12 +127,6 @@ LFCSetup::LFCSetup(asiolink::IntervalTimer::Callback callback)
LFCSetup::~LFCSetup() {
try {
- // If we're here it means that either the process is terminating
- // or we're reconfiguring the server. In both cases the thread has
- // probably been stopped already, but we make sure by calling
- // stopThread explicitly here.
- timer_mgr_->stopThread();
-
// Remove the timer. This will throw an exception if the timer does not
// exist. There are several possible reasons for this:
// a) It hasn't been registered (although if the LFC Setup instance
@@ -216,7 +210,7 @@ LFCSetup::setup(const uint32_t lfc_interval,
callback_();
}
- // If it's suposed to run periodically, setup that now.
+ // If it's supposed to run periodically, setup that now.
if (lfc_interval > 0) {
// Set the timer to call callback function periodically.
LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_SETUP).arg(lfc_interval);
@@ -332,7 +326,7 @@ public:
/// The result set is populated by iterating over the IPv4 leases in
/// storage, in ascending order by address, accumulating the lease state
/// counts per subnet. Note that walking the leases by address should
- /// inherently group them by subnet, and while this does not gaurantee
+ /// inherently group them by subnet, and while this does not guarantee
/// ascending order of subnet id, it should be sufficient to accumulate
/// state counts per subnet. This avoids introducing an additional
/// subnet_id index.
@@ -425,7 +419,7 @@ public:
/// The result set is populated by iterating over the IPv6 leases in
/// storage, in ascending order by address, accumulating the lease state
/// counts per subnet. Note that walking the leases by address should
- /// inherently group them by subnet, and while this does not gaurantee
+ /// inherently group them by subnet, and while this does not guarantee
/// ascending order of subnet id, it should be sufficient to accumulate
/// state counts per subnet. This avoids introducing an additional
/// subnet_id index.
@@ -556,7 +550,7 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const DatabaseConnection::ParameterMap& param
LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_NO_STORAGE);
} else {
if (conversion_needed) {
- LOG_WARN(dhcpsrv_logger, DHCPRSV_MEMFILE_CONVERTING_LEASE_FILES)
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_CONVERTING_LEASE_FILES)
.arg(MAJOR_VERSION).arg(MINOR_VERSION);
}
lfcSetup(conversion_needed);
@@ -644,14 +638,17 @@ Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_MEMFILE_GET_HWADDR).arg(hwaddr.toText());
Lease4Collection collection;
- const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
- for(Lease4StorageAddressIndex::const_iterator lease = idx.begin();
- lease != idx.end(); ++lease) {
- // Every Lease4 has a hardware address, so we can compare it
- if ( (*(*lease)->hwaddr_) == hwaddr) {
- collection.push_back((*lease));
- }
+ // Using composite index by 'hw address' and 'subnet id'. It is
+ // ok to use it for searching by the 'hw address' only.
+ const Lease4StorageHWAddressSubnetIdIndex& idx =
+ storage4_.get<HWAddressSubnetIdIndexTag>();
+ std::pair<Lease4StorageHWAddressSubnetIdIndex::const_iterator,
+ Lease4StorageHWAddressSubnetIdIndex::const_iterator> l
+ = idx.equal_range(boost::make_tuple(hwaddr.hwaddr_));
+
+ for(auto lease = l.first; lease != l.second; ++lease) {
+ collection.push_back(Lease4Ptr(new Lease4(**lease)));
}
return (collection);
@@ -683,15 +680,16 @@ Memfile_LeaseMgr::getLease4(const ClientId& client_id) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_MEMFILE_GET_CLIENTID).arg(client_id.toText());
Lease4Collection collection;
- const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
- for(Lease4StorageAddressIndex::const_iterator lease = idx.begin();
- lease != idx.end(); ++ lease) {
+ // Using composite index by 'client id' and 'subnet id'. It is ok
+ // to use it to search by 'client id' only.
+ const Lease4StorageClientIdSubnetIdIndex& idx =
+ storage4_.get<ClientIdSubnetIdIndexTag>();
+ std::pair<Lease4StorageClientIdSubnetIdIndex::const_iterator,
+ Lease4StorageClientIdSubnetIdIndex::const_iterator> l
+ = idx.equal_range(boost::make_tuple(client_id.getClientId()));
- // client-id is not mandatory in DHCPv4. There can be a lease that does
- // not have a client-id. Dereferencing null pointer would be a bad thing
- if((*lease)->client_id_ && *(*lease)->client_id_ == client_id) {
- collection.push_back((*lease));
- }
+ for(auto lease = l.first; lease != l.second; ++lease) {
+ collection.push_back(Lease4Ptr(new Lease4(**lease)));
}
return (collection);
@@ -993,12 +991,12 @@ Memfile_LeaseMgr::deleteExpiredReclaimedLeases(const uint32_t secs,
// This returns the first element which is greater than the specified
// tuple (true, time(NULL) - secs). However, the range between the
- // beginnng of the index and returned element also includes all the
+ // beginning of the index and returned element also includes all the
// elements for which the first value is false (lease state is NOT
// reclaimed), because false < true. All elements between the
// beginning of the index and the element returned, for which the
// first value is true, represent the reclaimed leases which should
- // be deleted, because their expiration time + secs has occured earlier
+ // be deleted, because their expiration time + secs has occurred earlier
// than current time.
typename IndexType::const_iterator upper_limit =
index.upper_bound(boost::make_tuple(true, time(NULL) - secs));
@@ -1325,5 +1323,62 @@ Memfile_LeaseMgr::startLeaseStatsQuery6() {
return(query);
}
+size_t Memfile_LeaseMgr::wipeLeases4(const SubnetID& subnet_id) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_WIPE_LEASES4)
+ .arg(subnet_id);
+
+ // Get the index by DUID, IAID, lease type.
+ const Lease4StorageSubnetIdIndex& idx = storage4_.get<SubnetIdIndexTag>();
+
+ // Try to get the lease using the DUID, IAID and lease type.
+ std::pair<Lease4StorageSubnetIdIndex::const_iterator,
+ Lease4StorageSubnetIdIndex::const_iterator> l =
+ idx.equal_range(subnet_id);
+
+ // Let's collect all leases.
+ Lease4Collection leases;
+ for(auto lease = l.first; lease != l.second; ++lease) {
+ leases.push_back(*lease);
+ }
+
+ size_t num = leases.size();
+ for (auto l = leases.begin(); l != leases.end(); ++l) {
+ deleteLease((*l)->addr_);
+ }
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_WIPE_LEASES4_FINISHED)
+ .arg(subnet_id).arg(num);
+
+ return (num);
+}
+
+size_t Memfile_LeaseMgr::wipeLeases6(const SubnetID& subnet_id) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_WIPE_LEASES6)
+ .arg(subnet_id);
+
+ // Get the index by DUID, IAID, lease type.
+ const Lease6StorageSubnetIdIndex& idx = storage6_.get<SubnetIdIndexTag>();
+
+ // Try to get the lease using the DUID, IAID and lease type.
+ std::pair<Lease6StorageSubnetIdIndex::const_iterator,
+ Lease6StorageSubnetIdIndex::const_iterator> l =
+ idx.equal_range(subnet_id);
+
+ // Let's collect all leases.
+ Lease6Collection leases;
+ for(auto lease = l.first; lease != l.second; ++lease) {
+ leases.push_back(*lease);
+ }
+
+ size_t num = leases.size();
+ for (auto l = leases.begin(); l != leases.end(); ++l) {
+ deleteLease((*l)->addr_);
+ }
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_WIPE_LEASES6_FINISHED)
+ .arg(subnet_id).arg(num);
+
+ return (num);
+}
+
+
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
index 1e57ef33cd..3d76f94468 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.h
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -332,6 +332,24 @@ public:
/// @return Number of leases deleted.
virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs);
+ /// @brief Removes specified IPv4 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases4(const SubnetID& subnet_id);
+
+ /// @brief Removed specified IPv6 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases6(const SubnetID& subnet_id);
+
private:
/// @brief Deletes all expired-reclaimed leases.
@@ -650,7 +668,7 @@ private:
/// the unit tests need to override this path (with the path in the
/// Kea build directory, the @c KEA_LFC_EXECUTABLE environmental
/// variable should be set to hold an absolute path to the kea-lfc
- /// excutable.
+ /// executable.
/// @param conversion_needed flag that indicates input lease file(s) are
/// schema do not match the current schema (older or newer), and need
/// conversion. This value is passed through to LFCSetup::setup() via its
diff --git a/src/lib/dhcpsrv/memfile_lease_storage.h b/src/lib/dhcpsrv/memfile_lease_storage.h
index 3b07fad238..059210f8aa 100644
--- a/src/lib/dhcpsrv/memfile_lease_storage.h
+++ b/src/lib/dhcpsrv/memfile_lease_storage.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -42,6 +42,9 @@ struct ClientIdSubnetIdIndexTag { };
/// @brief Tag for indexes by client id, HW address and subnet id.
struct ClientIdHWAddressSubnetIdIndexTag { };
+/// @brief Tag for indexs by subnet-id.
+struct SubnetIdIndexTag { };
+
/// @name Multi index containers holding DHCPv4 and DHCPv6 leases.
///
//@{
@@ -103,6 +106,13 @@ typedef boost::multi_index_container<
boost::multi_index::const_mem_fun<Lease, int64_t,
&Lease::getExpirationTime>
>
+ >,
+
+ // Specification of the fourth index starts here.
+ // This index sorts leases by SubnetID.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SubnetIdIndexTag>,
+ boost::multi_index::member<Lease, isc::dhcp::SubnetID, &Lease::subnet_id_>
>
>
> Lease6Storage; // Specify the type name of this container.
@@ -210,7 +220,15 @@ typedef boost::multi_index_container<
boost::multi_index::const_mem_fun<Lease, int64_t,
&Lease::getExpirationTime>
>
+ >,
+
+ // Specification of the sixth index starts here.
+ // This index sorts leases by SubnetID.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SubnetIdIndexTag>,
+ boost::multi_index::member<Lease, isc::dhcp::SubnetID, &Lease::subnet_id_>
>
+
>
> Lease4Storage; // Specify the type name for this container.
@@ -229,10 +247,13 @@ typedef Lease6Storage::index<DuidIaidTypeIndexTag>::type Lease6StorageDuidIaidTy
/// @brief DHCPv6 lease storage index by expiration time.
typedef Lease6Storage::index<ExpirationIndexTag>::type Lease6StorageExpirationIndex;
+/// @brief DHCPv6 lease storage index by Subnet-id.
+typedef Lease6Storage::index<SubnetIdIndexTag>::type Lease6StorageSubnetIdIndex;
+
/// @brief DHCPv4 lease storage index by address.
typedef Lease4Storage::index<AddressIndexTag>::type Lease4StorageAddressIndex;
-/// @brief DHCPv4 lease storage index by exiration time.
+/// @brief DHCPv4 lease storage index by expiration time.
typedef Lease4Storage::index<ExpirationIndexTag>::type Lease4StorageExpirationIndex;
/// @brief DHCPv4 lease storage index by HW address and subnet identifier.
@@ -247,6 +268,9 @@ Lease4StorageClientIdSubnetIdIndex;
typedef Lease4Storage::index<ClientIdHWAddressSubnetIdIndexTag>::type
Lease4StorageClientIdHWAddressSubnetIdIndex;
+/// @brief DHCPv4 lease storage index by client id, HW address and subnet id.
+typedef Lease4Storage::index<SubnetIdIndexTag>::type Lease4StorageSubnetIdIndex;
+
//@}
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/mysql_connection.cc b/src/lib/dhcpsrv/mysql_connection.cc
index 06162bdf1e..9d6954a6ca 100644
--- a/src/lib/dhcpsrv/mysql_connection.cc
+++ b/src/lib/dhcpsrv/mysql_connection.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -28,7 +28,8 @@ const my_bool MLM_TRUE = 1;
const int MLM_MYSQL_FETCH_SUCCESS = 0;
const int MLM_MYSQL_FETCH_FAILURE = 1;
-const int MYSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
+/// @todo: Migrate this default value to src/bin/dhcpX/simple_parserX.cc
+const int MYSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
MySqlTransaction::MySqlTransaction(MySqlConnection& conn)
: conn_(conn), committed_(false) {
@@ -65,6 +66,33 @@ MySqlConnection::openDatabase() {
// No host. Fine, we'll use "localhost"
}
+ unsigned int port = 0;
+ string sport;
+ try {
+ sport = getParameter("port");
+ } catch (...) {
+ // No port parameter, we are going to use the default port.
+ sport = "";
+ }
+
+ if (sport.size() > 0) {
+ // Port was given, so try to convert it to an integer.
+
+ try {
+ port = boost::lexical_cast<unsigned int>(sport);
+ } catch (...) {
+ // Port given but could not be converted to an unsigned int.
+ // Just fall back to the default value.
+ port = 0;
+ }
+
+ // The port is only valid when it is in the 0..65535 range.
+ // Again fall back to the default when the given value is invalid.
+ if (port > numeric_limits<uint16_t>::max()) {
+ port = 0;
+ }
+ }
+
const char* user = NULL;
string suser;
try {
@@ -115,7 +143,7 @@ MySqlConnection::openDatabase() {
}
// The timeout is only valid if greater than zero, as depending on the
- // database, a zero timeout might signify someting like "wait
+ // database, a zero timeout might signify something like "wait
// indefinitely".
//
// The check below also rejects a value greater than the maximum
@@ -172,7 +200,7 @@ MySqlConnection::openDatabase() {
// because no row matching the WHERE clause was found, or because a
// row was found but no data was altered.
MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
- 0, NULL, CLIENT_FOUND_ROWS);
+ port, NULL, CLIENT_FOUND_ROWS);
if (status != mysql_) {
isc_throw(DbOpenError, mysql_error(mysql_));
}
diff --git a/src/lib/dhcpsrv/mysql_connection.h b/src/lib/dhcpsrv/mysql_connection.h
index 6340e7e572..322b33fdae 100644
--- a/src/lib/dhcpsrv/mysql_connection.h
+++ b/src/lib/dhcpsrv/mysql_connection.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -41,7 +41,7 @@ extern const int MLM_MYSQL_FETCH_FAILURE;
/// @name Current database schema version values.
//@{
const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 5;
-const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 0;
+const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 1;
//@}
@@ -381,7 +381,7 @@ public:
switch(mysql_errno(mysql_)) {
// These are the ones we consider fatal. Remember this method is
// used to check errors of API calls made subsequent to successfully
- // connecting. Errors occuring while attempting to connect are
+ // connecting. Errors occurring while attempting to connect are
// checked in the connection code. An alternative would be to call
// mysql_ping() - assuming autoreconnect is off. If that fails
// then we know connection is toast.
diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc
index b829748c93..82fd38d6a5 100644
--- a/src/lib/dhcpsrv/mysql_host_data_source.cc
+++ b/src/lib/dhcpsrv/mysql_host_data_source.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -73,7 +73,7 @@ const size_t BOOT_FILE_NAME_MAX_LEN = 128;
///
/// This value is used to validate whether the identifier type stored in
/// a database is within bounds. of supported identifiers.
-const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::IDENT_CIRCUIT_ID);
+const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
/// @brief This class provides mechanisms for sending and retrieving
/// information from the 'hosts' table.
@@ -253,7 +253,7 @@ public:
// host_id : INT UNSIGNED NOT NULL
// The host_id is auto_incremented by MySQL database,
// so we need to pass the NULL value
- host_id_ = static_cast<uint32_t>(NULL);
+ host_id_ = 0;
bind_[0].buffer_type = MYSQL_TYPE_LONG;
bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
bind_[0].is_unsigned = MLM_TRUE;
@@ -295,11 +295,11 @@ public:
// The address in the Host structure is an IOAddress object. Convert
// this to an integer for storage.
ipv4_address_ = host->getIPv4Reservation().toUint32();
+ ipv4_address_null_ = ipv4_address_ == 0 ? MLM_TRUE : MLM_FALSE;
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
bind_[5].is_unsigned = MLM_TRUE;
- // bind_[5].is_null = &MLM_FALSE; // commented out for performance
- // reasons, see memset() above
+ bind_[5].is_null = &ipv4_address_null_;
// hostname : VARCHAR(255) NULL
strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
@@ -374,7 +374,7 @@ public:
// is_null only if it should be true. This gives up minor performance
// benefit while being safe approach. For improved readability, the
// code that explicitly sets is_null is there, but is commented out.
- // This also takes care of seeeting bind_[X].is_null to MLM_FALSE.
+ // This also takes care of setting bind_[X].is_null to MLM_FALSE.
memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
// host_id : INT UNSIGNED NOT NULL
@@ -575,7 +575,7 @@ public:
/// adding duplicated hosts to the collection, assuming that processed
/// rows are primarily ordered by host id column.
///
- /// This method must be overriden in the derived classes to also
+ /// This method must be overridden in the derived classes to also
/// retrieve IPv6 reservations and DHCP options associated with a host.
///
/// @param [out] hosts Collection of hosts to which a new host created
@@ -1184,7 +1184,7 @@ private:
/// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
///
/// This class extends the @ref MySqlHostWithOptionsExchange class with the
-/// mechanisms to retrieve IPv6 reservations. This class is used in sitations
+/// mechanisms to retrieve IPv6 reservations. This class is used in situations
/// when it is desired to retrieve DHCPv6 specific information about the host
/// (DHCPv6 options and reservations), or entire information about the host
/// (DHCPv4 options, DHCPv6 options and reservations). The following are the
@@ -1747,7 +1747,7 @@ public:
/// @brief Statement Tags
///
/// The contents of the enum are indexes into the list of SQL statements.
- /// It is assumed that the order is such that the indicies of statements
+ /// It is assumed that the order is such that the indices of statements
/// reading the database are less than those of statements modifying the
/// database.
enum StatementIndex {
@@ -1763,6 +1763,9 @@ public:
INSERT_V6_RESRV, // Insert v6 reservation
INSERT_V4_OPTION, // Insert DHCPv4 option
INSERT_V6_OPTION, // Insert DHCPv6 option
+ DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
+ DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
+ DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
NUM_STATEMENTS // Number of statements
};
@@ -1792,6 +1795,15 @@ public:
void addStatement(MySqlHostDataSourceImpl::StatementIndex stindex,
std::vector<MYSQL_BIND>& bind);
+ /// @brief Executes statements that delete records.
+ ///
+ /// @param stindex Index of a statement being executed.
+ /// @param bind Vector of MYSQL_BIND objects to be used when making the
+ /// query.
+ /// @return true if any records were deleted, false otherwise
+ bool
+ delStatement(StatementIndex stindex, MYSQL_BIND* bind);
+
/// @brief Inserts IPv6 Reservation into ipv6_reservation table.
///
/// @param resv IPv6 Reservation to be added
@@ -1862,7 +1874,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
/// @param stindex Statement index.
@@ -2101,7 +2113,20 @@ TaggedStatementArray tagged_statements = { {
{MySqlHostDataSourceImpl::INSERT_V6_OPTION,
"INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
"persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) "
- " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"}}
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
+
+ {MySqlHostDataSourceImpl::DEL_HOST_ADDR4,
+ "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
+
+ {MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
+ "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type=? "
+ "AND dhcp_identifier = ?"},
+
+ {MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
+ "DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type=? "
+ "AND dhcp_identifier = ?"}
+
+ }
};
MySqlHostDataSourceImpl::
@@ -2186,6 +2211,25 @@ MySqlHostDataSourceImpl::addStatement(StatementIndex stindex,
}
}
+bool
+MySqlHostDataSourceImpl::delStatement(StatementIndex stindex,
+ MYSQL_BIND* bind) {
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
+ checkError(status, stindex, "unable to bind parameters");
+
+ // Execute the statement
+ status = mysql_stmt_execute(conn_.statements_[stindex]);
+
+ if (status != 0) {
+ checkError(status, stindex, "unable to execute");
+ }
+
+ // Let's check how many hosts were deleted.
+ my_ulonglong numrows = mysql_stmt_affected_rows(conn_.statements_[stindex]);
+ return (numrows != 0);
+}
+
void
MySqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
const HostID& id) {
@@ -2412,6 +2456,113 @@ MySqlHostDataSource::add(const HostPtr& host) {
transaction.commit();
}
+bool
+MySqlHostDataSource::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly();
+
+ if (addr.isV4()) {
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+
+ uint32_t subnet = subnet_id;
+ memset(inbind, 0, sizeof(inbind));
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ uint32_t addr4 = addr.toUint32();
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_ADDR4, inbind));
+ }
+
+ // v6
+ ConstHostPtr host = get6(subnet_id, addr);
+ if (!host) {
+ return (false);
+ }
+
+ // Ok, there is a host. Let's delete it.
+ return del6(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0],
+ host->getIdentifier().size());
+}
+
+bool
+MySqlHostDataSource::del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) {
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly();
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+
+ // subnet-id
+ memset(inbind, 0, sizeof(inbind));
+ uint32_t subnet = static_cast<uint32_t>(subnet_id);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // identifier type
+ char identifier_type_copy = static_cast<char>(identifier_type);
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // identifier value
+ std::vector<char> identifier_vec(identifier_begin,
+ identifier_begin + identifier_len);
+ unsigned long length = identifier_vec.size();
+ inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[2].buffer = &identifier_vec[0];
+ inbind[2].buffer_length = length;
+ inbind[2].length = &length;
+
+ ConstHostCollection collection;
+ return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID, inbind));
+}
+
+bool
+MySqlHostDataSource::del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) {
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly();
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+
+ // subnet-id
+ memset(inbind, 0, sizeof(inbind));
+ uint32_t subnet = static_cast<uint32_t>(subnet_id);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // identifier type
+ char identifier_type_copy = static_cast<char>(identifier_type);
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // identifier value
+ std::vector<char> identifier_vec(identifier_begin,
+ identifier_begin + identifier_len);
+ unsigned long length = identifier_vec.size();
+ inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[2].buffer = &identifier_vec[0];
+ inbind[2].buffer_length = length;
+ inbind[2].length = &length;
+
+ ConstHostCollection collection;
+ return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, inbind));
+}
+
ConstHostCollection
MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
const DuidPtr& duid) const {
diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h
index fe7a592037..11e52bd5e3 100644
--- a/src/lib/dhcpsrv/mysql_host_data_source.h
+++ b/src/lib/dhcpsrv/mysql_host_data_source.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -86,7 +86,7 @@ public:
/// because a particular client may have reservations in multiple subnets.
///
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -109,7 +109,7 @@ public:
/// @brief Returns a host connected to the IPv4 subnet.
///
/// Implementations of this method should guard against the case when
- /// mutliple instances of the @c Host are present, e.g. when two
+ /// multiple instances of the @c Host are present, e.g. when two
/// @c Host objects are found, one for the DUID, another one for the
/// HW address. In such case, an implementation of this method
/// should throw an MultipleRecords exception.
@@ -128,7 +128,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -160,7 +160,7 @@ public:
/// @brief Returns a host connected to the IPv6 subnet.
///
/// Implementations of this method should guard against the case when
- /// mutliple instances of the @c Host are present, e.g. when two
+ /// multiple instances of the @c Host are present, e.g. when two
/// @c Host objects are found, one for the DUID, another one for the
/// HW address. In such case, an implementation of this method
/// should throw an MultipleRecords exception.
@@ -179,7 +179,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -220,6 +220,40 @@ public:
/// @param host Pointer to the new @c Host object being added.
virtual void add(const HostPtr& host);
+ /// @brief Attempts to delete a host by (subnet-id, address)
+ ///
+ /// This method supports both v4 and v6.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr);
+
+ /// @brief Attempts to delete a host by (subnet4-id, identifier type, identifier)
+ ///
+ /// This method supports v4 hosts only.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Attempts to delete a host by (subnet6-id, identifier type, identifier)
+ ///
+ /// This method supports v6 hosts only.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
/// @brief Return backend type
///
/// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index 570e611dd0..63d4cabdc3 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -46,7 +46,7 @@ using namespace std;
/// - data being retrieved from the database (as in getting lease information)
/// - selection criteria used to determine which records to update/retrieve.
///
-/// All such data is associated with the prepared statment using an array of
+/// All such data is associated with the prepared statement using an array of
/// MYSQL_BIND structures. Each element in the array corresponds to one
/// parameter in the prepared statement - the first element in the array is
/// associated with the first parameter, the second element with the second
@@ -71,7 +71,7 @@ using namespace std;
/// lease object.
namespace {
-
+
/// @brief Maximum length of the hostname stored in DNS.
///
/// This length is restricted by the length of the domain-name carried
@@ -226,7 +226,7 @@ namespace dhcp {
/// @brief Common MySQL and Lease Data Methods
///
/// The MySqlLease4Exchange and MySqlLease6Exchange classes provide the
-/// functionaility to set up binding information between variables in the
+/// functionality to set up binding information between variables in the
/// program and data extracted from the database. This class is the common
/// base to both of them, containing some common methods.
@@ -752,7 +752,7 @@ public:
// is guaranteed to be valid until the next non-const operation on
// addr6_.)
//
- // The const_cast could be avoided by copying the string to a writeable
+ // The const_cast could be avoided by copying the string to a writable
// buffer and storing the address of that in the "buffer" element.
// However, this introduces a copy operation (with additional overhead)
// purely to get round the structures introduced by design of the
@@ -1227,7 +1227,7 @@ private:
///
/// This class is used to recalculate lease statistics for MySQL
/// lease storage. It does so by executing a query which returns a result
-/// containining contain one row per monitored state per lease type per
+/// containing one row per monitored state per lease type per
/// subnet, ordered by subnet id in ascending order.
///
class MySqlLeaseStatsQuery : public LeaseStatsQuery {
@@ -1549,7 +1549,7 @@ void MySqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
void MySqlLeaseMgr::getLease(StatementIndex stindex, MYSQL_BIND* bind,
Lease4Ptr& result) const {
// Create appropriate collection object and get all leases matching
- // the selection criteria. The "single" paraeter is true to indicate
+ // the selection criteria. The "single" parameter is true to indicate
// that the called method should throw an exception if multiple
// matching records are found: this particular method is called when only
// one or zero matches is expected.
@@ -1568,7 +1568,7 @@ void MySqlLeaseMgr::getLease(StatementIndex stindex, MYSQL_BIND* bind,
void MySqlLeaseMgr::getLease(StatementIndex stindex, MYSQL_BIND* bind,
Lease6Ptr& result) const {
// Create appropriate collection object and get all leases matching
- // the selection criteria. The "single" paraeter is true to indicate
+ // the selection criteria. The "single" parameter is true to indicate
// that the called method should throw an exception if multiple
// matching records are found: this particular method is called when only
// one or zero matches is expected.
@@ -1786,7 +1786,7 @@ MySqlLeaseMgr::getLeases6(Lease::Type lease_type,
// the "const" is discarded before the uint8_t* is cast to char*.
//
// Note that the const_cast could be avoided by copying the DUID to
- // a writeable buffer and storing the address of that in the "buffer"
+ // a writable buffer and storing the address of that in the "buffer"
// element. However, this introduces a copy operation (with additional
// overhead) purely to get round the structures introduced by design of
// the MySQL interface (which uses the area pointed to by "buffer" as
@@ -2100,7 +2100,15 @@ MySqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
return (deleted_leases);
}
+size_t
+MySqlLeaseMgr::wipeLeases4(const SubnetID& /*subnet_id*/) {
+ isc_throw(NotImplemented, "wipeLeases4 is not implemented for MySQL backend");
+}
+size_t
+MySqlLeaseMgr::wipeLeases6(const SubnetID& /*subnet_id*/) {
+ isc_throw(NotImplemented, "wipeLeases6 is not implemented for MySQL backend");
+}
// Miscellaneous database methods.
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index 86e9b9bfb6..5c47e8ede9 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -329,6 +329,28 @@ public:
/// @return Number of leases deleted.
virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t secs);
+ /// @brief Removes specified IPv4 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @todo: Not implemented yet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases4(const SubnetID& subnet_id);
+
+ /// @brief Removed specified IPv6 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @todo: Not implemented yet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases6(const SubnetID& subnet_id);
+
/// @brief Deletes all expired-reclaimed DHCPv6 leases.
///
/// @param secs Number of seconds since expiration of leases before
@@ -409,8 +431,8 @@ public:
INSERT_LEASE6, // Add entry to lease6 table
UPDATE_LEASE4, // Update a Lease4 entry
UPDATE_LEASE6, // Update a Lease6 entry
- RECOUNT_LEASE4_STATS, // Fetches IPv4 address statisics
- RECOUNT_LEASE6_STATS, // Fetches IPv6 address statisics
+ RECOUNT_LEASE4_STATS, // Fetches IPv4 address statistics
+ RECOUNT_LEASE6_STATS, // Fetches IPv6 address statistics
NUM_STATEMENTS // Number of statements
};
@@ -421,7 +443,7 @@ private:
/// of the addLease method. It binds the contents of the lease object to
/// the prepared statement and adds it to the database.
///
- /// @param stindex Index of statemnent being executed
+ /// @param stindex Index of statement being executed
/// @param bind MYSQL_BIND array that has been created for the type
/// of lease in question.
///
@@ -502,7 +524,7 @@ private:
///
/// This method performs the common actions for the various getLease4()
/// methods. It acts as an interface to the getLeaseCollection() method,
- /// but retrieveing only a single lease.
+ /// but retrieving only a single lease.
///
/// @param stindex Index of statement being executed
/// @param bind MYSQL_BIND array for input parameters
@@ -514,7 +536,7 @@ private:
///
/// This method performs the common actions for the various getLease46)
/// methods. It acts as an interface to the getLeaseCollection() method,
- /// but retrieveing only a single lease.
+ /// but retrieving only a single lease.
///
/// @param stindex Index of statement being executed
/// @param bind MYSQL_BIND array for input parameters
diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
index c939a4eaa4..01d35beb25 100644
--- a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
+++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
@@ -1,13 +1,15 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#include <cc/data.h>
+#include <config.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
#include <eval/eval_context.h>
#include <asiolink/io_address.h>
#include <asiolink/io_error.h>
@@ -18,7 +20,7 @@ using namespace isc::data;
using namespace isc::asiolink;
using namespace std;
-/// @file client_class_def.cc
+/// @file client_class_def_parser.cc
///
/// @brief Method implementations for client class definition parsing
@@ -27,14 +29,10 @@ namespace dhcp {
// ********************** ExpressionParser ****************************
-ExpressionParser::ExpressionParser(const std::string&,
- ExpressionPtr& expression, ParserContextPtr global_context)
- : local_expression_(ExpressionPtr()), expression_(expression),
- global_context_(global_context) {
-}
-
void
-ExpressionParser::build(ConstElementPtr expression_cfg) {
+ExpressionParser::parse(ExpressionPtr& expression,
+ ConstElementPtr expression_cfg,
+ uint16_t family) {
if (expression_cfg->getType() != Element::string) {
isc_throw(DhcpConfigError, "expression ["
<< expression_cfg->str() << "] must be a string, at ("
@@ -46,10 +44,10 @@ ExpressionParser::build(ConstElementPtr expression_cfg) {
std::string value;
expression_cfg->getValue(value);
try {
- EvalContext eval_ctx(global_context_->universe_);
+ EvalContext eval_ctx(family == AF_INET ? Option::V4 : Option::V6);
eval_ctx.parseString(value);
- local_expression_.reset(new Expression());
- *local_expression_ = eval_ctx.expression;
+ expression.reset(new Expression());
+ *expression = eval_ctx.expression;
} catch (const std::exception& ex) {
// Append position if there is a failure.
isc_throw(DhcpConfigError,
@@ -59,141 +57,113 @@ ExpressionParser::build(ConstElementPtr expression_cfg) {
}
}
-void
-ExpressionParser::commit() {
- expression_ = local_expression_;
-}
-
// ********************** ClientClassDefParser ****************************
-ClientClassDefParser::ClientClassDefParser(const std::string&,
- ClientClassDictionaryPtr& class_dictionary, ParserContextPtr global_context)
- : string_values_(new StringStorage()),
- match_expr_(ExpressionPtr()),
- options_(new CfgOption()),
- class_dictionary_(class_dictionary),
- global_context_(global_context) {
-}
-
void
-ClientClassDefParser::build(ConstElementPtr class_def_cfg) {
-
- // Parse the elements that make up the option definition.
- BOOST_FOREACH(ConfigPair param, class_def_cfg->mapValue()) {
- std::string entry(param.first);
- ParserPtr parser;
- if (entry == "name") {
- StringParserPtr str_parser(new StringParser(entry, string_values_));
- parser = str_parser;
- } else if (entry == "test") {
- ExpressionParserPtr exp_parser(new ExpressionParser(entry,
- match_expr_,
- global_context_));
- parser = exp_parser;
- } else if (entry == "option-data") {
- OptionDataListParserPtr opts_parser;
- uint16_t family = (global_context_->universe_ == Option::V4 ?
- AF_INET : AF_INET6);
-
- opts_parser.reset(new OptionDataListParser(entry, options_, family));
- parser = opts_parser;
- } else if (entry == "next-server") {
- StringParserPtr str_parser(new StringParser(entry, string_values_));
- parser = str_parser;
- } else if (entry == "server-hostname") {
- StringParserPtr str_parser(new StringParser(entry, string_values_));
- parser = str_parser;
-
- } else if (entry == "boot-file-name") {
- StringParserPtr str_parser(new StringParser(entry, string_values_));
- parser = str_parser;
- } else {
- isc_throw(DhcpConfigError, "invalid parameter '" << entry
- << "' (" << param.second->getPosition() << ")");
- }
+ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
+ ConstElementPtr class_def_cfg,
+ uint16_t family) {
+ // name is now mandatory
+ std::string name = getString(class_def_cfg, "name");
+ if (name.empty()) {
+ isc_throw(DhcpConfigError,
+ "not empty parameter 'name' is required "
+ << getPosition("name", class_def_cfg) << ")");
+ }
- parser->build(param.second);
- parser->commit();
+ // Parse matching expression
+ ExpressionPtr match_expr;
+ ConstElementPtr test_cfg = class_def_cfg->get("test");
+ std::string test;
+ if (test_cfg) {
+ ExpressionParser parser;
+ parser.parse(match_expr, test_cfg, family);
+ test = test_cfg->stringValue();
}
- std::string name;
- try {
- name = string_values_->getParam("name");
+ // Parse option data
+ CfgOptionPtr options(new CfgOption());
+ ConstElementPtr option_data = class_def_cfg->get("option-data");
+ if (option_data) {
+ OptionDataListParser opts_parser(family);
+ opts_parser.parse(options, option_data);
+ }
- // Let's parse the next-server field
- IOAddress next_server("0.0.0.0");
- string next_server_txt = string_values_->getOptionalParam("next-server", "0.0.0.0");
+ // Let's try to parse the next-server field
+ IOAddress next_server("0.0.0.0");
+ if (class_def_cfg->contains("next-server")) {
+ std::string next_server_txt = getString(class_def_cfg, "next-server");
try {
next_server = IOAddress(next_server_txt);
} catch (const IOError& ex) {
- isc_throw(DhcpConfigError, "Invalid next-server value specified: '"
- << next_server_txt);
+ isc_throw(DhcpConfigError,
+ "Invalid next-server value specified: '"
+ << next_server_txt << "' ("
+ << getPosition("next-server", class_def_cfg) << ")");
}
if (next_server.getFamily() != AF_INET) {
isc_throw(DhcpConfigError, "Invalid next-server value: '"
- << next_server_txt << "', must be IPv4 address");
+ << next_server_txt << "', must be IPv4 address ("
+ << getPosition("next-server", class_def_cfg) << ")");
}
if (next_server.isV4Bcast()) {
isc_throw(DhcpConfigError, "Invalid next-server value: '"
- << next_server_txt << "', must not be a broadcast");
+ << next_server_txt << "', must not be a broadcast ("
+ << getPosition("next-server", class_def_cfg) << ")");
}
+ }
+
+ // Let's try to parse server-hostname
+ std::string sname;
+ if (class_def_cfg->contains("server-hostname")) {
+ sname = getString(class_def_cfg, "server-hostname");
- // Let's try to parse sname
- string sname = string_values_->getOptionalParam("server-hostname", "");
if (sname.length() >= Pkt4::MAX_SNAME_LEN) {
isc_throw(DhcpConfigError, "server-hostname must be at most "
<< Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is "
- << sname.length());
+ << sname.length() << " ("
+ << getPosition("server-hostname", class_def_cfg) << ")");
}
+ }
+
+ // Let's try to parse boot-file-name
+ std::string filename;
+ if (class_def_cfg->contains("boot-file-name")) {
+ filename = getString(class_def_cfg, "boot-file-name");
- string filename = string_values_->getOptionalParam("boot-file-name", "");
if (filename.length() > Pkt4::MAX_FILE_LEN) {
isc_throw(DhcpConfigError, "boot-file-name must be at most "
<< Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is "
- << filename.length());
+ << filename.length() << " ("
+ << getPosition("boot-file-name", class_def_cfg) << ")");
}
- // an OptionCollectionPtr
- class_dictionary_->addClass(name, match_expr_, options_, next_server,
- sname, filename);
+ }
+
+ // Add the client class definition
+ try {
+ class_dictionary->addClass(name, match_expr, test, options,
+ next_server, sname, filename);
} catch (const std::exception& ex) {
- isc_throw(DhcpConfigError, ex.what()
+ isc_throw(DhcpConfigError, "Can't add class: " << ex.what()
<< " (" << class_def_cfg->getPosition() << ")");
}
}
// ****************** ClientClassDefListParser ************************
-ClientClassDefListParser::ClientClassDefListParser(const std::string&,
- ParserContextPtr
- global_context)
- : local_dictionary_(new ClientClassDictionary()),
- global_context_(global_context) {
-}
-
-void
-ClientClassDefListParser::build(ConstElementPtr client_class_def_list) {
- if (!client_class_def_list) {
- isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
- << " client class definitions is NULL ("
- << client_class_def_list->getPosition() << ")");
- }
-
+ClientClassDictionaryPtr
+ClientClassDefListParser::parse(ConstElementPtr client_class_def_list,
+ uint16_t family) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
BOOST_FOREACH(ConstElementPtr client_class_def,
client_class_def_list->listValue()) {
- boost::shared_ptr<ClientClassDefParser>
- parser(new ClientClassDefParser("client-class-def",
- local_dictionary_,
- global_context_));
- parser->build(client_class_def);
+ ClientClassDefParser parser;
+ parser.parse(dictionary, client_class_def, family);
}
-}
-
-void
-ClientClassDefListParser::commit() {
- CfgMgr::instance().getStagingCfg()->setClientClassDictionary(local_dictionary_);
+ return (dictionary);
}
} // end of namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.h b/src/lib/dhcpsrv/parsers/client_class_def_parser.h
index bb63aeaf4b..392162bc0b 100644
--- a/src/lib/dhcpsrv/parsers/client_class_def_parser.h
+++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015, 2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,10 +7,11 @@
#ifndef CLIENT_CLASS_DEF_PARSER_H
#define CLIENT_CLASS_DEF_PARSER_H
+#include <cc/data.h>
+#include <cc/simple_parser.h>
#include <dhcpsrv/client_class_def.h>
-#include <dhcpsrv/parsers/dhcp_parsers.h>
-/// @file client_class_def.h
+/// @file client_class_def_parser.h
///
/// @brief Parsers for client class definitions
///
@@ -22,11 +23,10 @@
/// There parsers defined are:
///
/// ClientClassDefListParser - creates a ClientClassDictionary from a list
-/// of element maps, where each map contains the entries that specifiy a
+/// of element maps, where each map contains the entries that specify a
/// single class. The names of the classes in the are expected to be
/// unique. Attempting to define a duplicate class will result in an
-/// DhcpConfigError throw. Invoking @c commit() method causes the dictionary
-/// to be stored by the CfgMgr.
+/// DhcpConfigError throw. At the end the dictionary is stored by the CfgMgr.
///
/// ClientClassDefParser - creates a ClientClassDefinition from an element
/// map. The elements are as follows:
@@ -37,7 +37,7 @@
/// membership in the class. This is passed into the eval parser.
///
/// -# "option-data" - a list which defines the options that should be
-/// assigned to memebers of the class. This element is optional and parsed
+/// assigned to remembers of the class. This element is optional and parsed
/// using the @ref isc::dhcp::OptionDataListParser.
///
/// ExpressionParser - creates an eval::Expression from a string element,
@@ -51,87 +51,37 @@ namespace dhcp {
/// This parser creates an instance of an Expression from a string. The
/// string is passed to the Eval Parser and the resultant Expression is
/// stored into the ExpressionPtr reference passed into the constructor.
-class ExpressionParser : public DhcpConfigParser {
+class ExpressionParser : public isc::data::SimpleParser {
public:
- /// @brief Constructor.
- ///
- /// @param dummy first argument is ignored, all Parser constructors
- /// accept string as first argument.
- /// @param expression variable in which to store the new expression
- /// @param global_context is a pointer to the global context which
- /// stores global scope parameters, options, option defintions.
- ExpressionParser(const std::string& dummy, ExpressionPtr& expression,
- ParserContextPtr global_context);
/// @brief Parses an expression configuration element into an Expression
///
+ /// @param expression variable in which to store the new expression
/// @param expression_cfg the configuration entry to be parsed.
+ /// @param family the address family of the expression.
///
/// @throw DhcpConfigError if parsing was unsuccessful.
- void build(isc::data::ConstElementPtr expression_cfg);
-
- /// @brief Stores the parsed expression to the supplied storage.
- void commit();
-
-private:
- /// @brief Local storage for the parsed expression
- ExpressionPtr local_expression_;
-
- /// @brief Storage into which the parsed expression should be committed
- ExpressionPtr& expression_;
-
- /// @brief Parsing context which contains global values, options and option
- /// definitions.
- ParserContextPtr global_context_;
+ void parse(ExpressionPtr& expression,
+ isc::data::ConstElementPtr expression_cfg, uint16_t family);
};
-typedef boost::shared_ptr<ExpressionParser> ExpressionParserPtr;
-
/// @brief Parser for a single client class definition.
///
/// This parser creates an instance of a client class definition.
-class ClientClassDefParser : public DhcpConfigParser {
+class ClientClassDefParser : public isc::data::SimpleParser {
public:
- /// @brief Constructor.
- ///
- /// @param dummy first argument is ignored, all Parser constructors
- /// accept string as first argument.
- /// @param class_dictionary dictionary into which the class should be added
- /// @param global_context is a pointer to the global context which
- /// stores global scope parameters, options, option defintions.
- ClientClassDefParser(const std::string& dummy,
- ClientClassDictionaryPtr& class_dictionary,
- ParserContextPtr global_context);
/// @brief Parses an entry that describes single client class definition.
///
- /// Attempts to add the new class direclty into the given dictionary.
+ /// Attempts to add the new class directly into the given dictionary.
/// This done here to detect duplicate classes prior to commit().
+ /// @param class_dictionary dictionary into which the class should be added
/// @param client_class_def a configuration entry to be parsed.
+ /// @param family the address family of the client class.
///
/// @throw DhcpConfigError if parsing was unsuccessful.
- void build(isc::data::ConstElementPtr client_class_def);
-
- /// @brief Does nothing.
- void commit() {};
-
-private:
-
- /// @brief Storage for class string values.
- StringStoragePtr string_values_;
-
- /// @brief Storage for the class match expression
- ExpressionPtr match_expr_;
-
- /// @brief Storage for the class options
- CfgOptionPtr options_;
-
- /// @brief Dictionary to which the new class should be added
- ClientClassDictionaryPtr class_dictionary_;
-
- /// @brief Parsing context which contains global values, options and option
- /// definitions.
- ParserContextPtr global_context_;
+ void parse(ClientClassDictionaryPtr& class_dictionary,
+ isc::data::ConstElementPtr client_class_def, uint16_t family);
};
/// @brief Defines a pointer to a ClientClassDefParser
@@ -141,44 +91,26 @@ typedef boost::shared_ptr<ClientClassDefParser> ClientClassDefParserPtr;
///
/// This parser iterates over all configuration entries that define
/// client classes and creates ClientClassDef instances for each.
-/// If the parsing done in build() is successful, the collection of
+/// When the parsing successfully completes, the collection of
/// created definitions is given to the CfgMgr.
-class ClientClassDefListParser : public DhcpConfigParser {
+class ClientClassDefListParser : public isc::data::SimpleParser {
public:
- /// @brief Constructor.
- ///
- /// @param dummy first argument is ignored, all Parser constructors
- /// accept string as first argument.
- /// @param global_context is a pointer to the global context which
- /// stores global scope parameters, options, option defintions.
- ClientClassDefListParser(const std::string& dummy,
- ParserContextPtr global_context);
/// @brief Parse configuration entries.
///
/// This function parses configuration entries, creates instances
- /// of client class definitions and tries to adds them to the a
- /// local dictionary.
+ /// of client class definitions and tries to adds them to the
+ /// local dictionary. At the end the dictionary is returned.
///
/// @param class_def_list pointer to an element that holds entries
/// for client class definitions.
+ /// @param family the address family of the client class definitions.
+ /// @return a pointer to the filled dictionary
/// @throw DhcpConfigError if configuration parsing fails.
- void build(isc::data::ConstElementPtr class_def_list);
-
- /// @brief Commits class definitions to CfgMgr's global storage.
- void commit();
-
- /// @brief Local class dictionary to store classes as they are being parsed
- ClientClassDictionaryPtr local_dictionary_;
-
- /// Parsing context which contains global values, options and option
- /// definitions.
- ParserContextPtr global_context_;
+ ClientClassDictionaryPtr
+ parse(isc::data::ConstElementPtr class_def_list, uint16_t family);
};
-/// @brief Defines a pointer to a ClientClassDefListParser
-typedef boost::shared_ptr<ClientClassDefListParser> ClientClassDefListParserPtr;
-
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/dbaccess_parser.cc b/src/lib/dhcpsrv/parsers/dbaccess_parser.cc
index 6b7fa51eec..e4231dc1e2 100644
--- a/src/lib/dhcpsrv/parsers/dbaccess_parser.cc
+++ b/src/lib/dhcpsrv/parsers/dbaccess_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,6 +13,7 @@
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/parsers/dbaccess_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
@@ -29,13 +30,14 @@ namespace dhcp {
// Factory function to build the parser
-DbAccessParser::DbAccessParser(const std::string&, DBType db_type)
+DbAccessParser::DbAccessParser(DBType db_type)
: values_(), type_(db_type) {
}
// Parse the configuration and check that the various keywords are consistent.
void
-DbAccessParser::build(isc::data::ConstElementPtr config_value) {
+DbAccessParser::parse(CfgDbAccessPtr& cfg_db,
+ ConstElementPtr database_config) {
// To cope with incremental updates, the strategy is:
// 1. Take a copy of the stored keyword/value pairs.
@@ -45,13 +47,17 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
// 5. Save resulting database access string in the Configuration
// Manager.
+ // Note only range checks can fail with a database_config from
+ // a flex/bison parser.
+
// 1. Take a copy of the stored keyword/value pairs.
std::map<string, string> values_copy = values_;
int64_t lfc_interval = 0;
int64_t timeout = 0;
+ int64_t port = 0;
// 2. Update the copy with the passed keywords.
- BOOST_FOREACH(ConfigPair param, config_value->mapValue()) {
+ BOOST_FOREACH(ConfigPair param, database_config->mapValue()) {
try {
if ((param.first == "persist") || (param.first == "readonly")) {
values_copy[param.first] = (param.second->boolValue() ?
@@ -67,12 +73,17 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
values_copy[param.first] =
boost::lexical_cast<std::string>(timeout);
+ } else if (param.first == "port") {
+ port = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(port);
+
} else {
values_copy[param.first] = param.second->stringValue();
}
} catch (const isc::data::TypeError& ex) {
// Append position of the element.
- isc_throw(BadValue, "invalid value type specified for "
+ isc_throw(DhcpConfigError, "invalid value type specified for "
"parameter '" << param.first << "' ("
<< param.second->getPosition() << ")");
}
@@ -83,33 +94,54 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
// a. Check if the "type" keyword exists and thrown an exception if not.
StringPairMap::const_iterator type_ptr = values_copy.find("type");
if (type_ptr == values_copy.end()) {
- isc_throw(TypeKeywordMissing, (type_ == LEASE_DB ? "lease" : "host")
+ isc_throw(DhcpConfigError, (type_ == LEASE_DB ? "lease" : "host")
<< " database access parameters must "
"include the keyword 'type' to determine type of database "
- "to be accessed (" << config_value->getPosition() << ")");
+ "to be accessed (" << database_config->getPosition() << ")");
}
// b. Check if the 'type' keyword known and throw an exception if not.
+ //
+ // Please note when you add a new database backend you have to add
+ // the new type here and in server grammars.
string dbtype = type_ptr->second;
- if ((dbtype != "memfile") && (dbtype != "mysql") && (dbtype != "postgresql") && (dbtype != "cql")) {
- isc_throw(BadValue, "unknown backend database type: " << dbtype
- << " (" << config_value->getPosition() << ")");
+ if ((dbtype != "memfile") &&
+ (dbtype != "mysql") &&
+ (dbtype != "postgresql") &&
+ (dbtype != "cql")) {
+ ConstElementPtr value = database_config->get("type");
+ isc_throw(DhcpConfigError, "unknown backend database type: " << dbtype
+ << " (" << value->getPosition() << ")");
}
- // c. Check that the lfc-interval is a number and it is within a resonable
- // range.
- if ((lfc_interval < 0) || (lfc_interval > std::numeric_limits<uint32_t>::max())) {
- isc_throw(BadValue, "lfc-interval value: " << lfc_interval
+ // c. Check that the lfc-interval is within a reasonable range.
+ if ((lfc_interval < 0) ||
+ (lfc_interval > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("lfc-interval");
+ isc_throw(DhcpConfigError, "lfc-interval value: " << lfc_interval
<< " is out of range, expected value: 0.."
- << std::numeric_limits<uint32_t>::max());
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
}
- // d. Check that the timeout is a number and it is within a resonable
- // range.
- if ((timeout < 0) || (timeout > std::numeric_limits<uint32_t>::max())) {
- isc_throw(BadValue, "timeout value: " << timeout
+ // d. Check that the timeout is within a reasonable range.
+ if ((timeout < 0) ||
+ (timeout > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("connect-timeout");
+ isc_throw(DhcpConfigError, "connect-timeout value: " << timeout
<< " is out of range, expected value: 0.."
- << std::numeric_limits<uint32_t>::max());
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
+
+ // e. Check that the port is within a reasonable range.
+ if ((port < 0) ||
+ (port > std::numeric_limits<uint16_t>::max())) {
+ ConstElementPtr value = database_config->get("port");
+ isc_throw(DhcpConfigError, "port value: " << port
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint16_t>::max()
+ << " (" << value->getPosition() << ")");
}
// 4. If all is OK, update the stored keyword/value pairs. We do this by
@@ -119,7 +151,6 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
values_.swap(values_copy);
// 5. Save the database access string in the Configuration Manager.
- CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
if (type_ == LEASE_DB) {
cfg_db->setLeaseDbAccessString(getDbAccessString());
@@ -152,11 +183,5 @@ DbAccessParser::getDbAccessString() const {
return (dbaccess);
}
-// Commit the changes - reopen the database with the new parameters
-void
-DbAccessParser::commit() {
-}
-
}; // namespace dhcp
}; // namespace isc
-
diff --git a/src/lib/dhcpsrv/parsers/dbaccess_parser.h b/src/lib/dhcpsrv/parsers/dbaccess_parser.h
index 6dc7e8c905..4e662d43f1 100644
--- a/src/lib/dhcpsrv/parsers/dbaccess_parser.h
+++ b/src/lib/dhcpsrv/parsers/dbaccess_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,8 +8,8 @@
#define DBACCESS_PARSER_H
#include <cc/data.h>
-#include <dhcpsrv/parsers/dhcp_config_parser.h>
-#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/cfg_db_access.h>
#include <exceptions/exceptions.h>
#include <string>
@@ -17,16 +17,6 @@
namespace isc {
namespace dhcp {
-/// @brief Exception thrown when 'type' keyword is missing from string
-///
-/// This condition is checked, but should never occur because 'type' is marked
-/// as mandatory in the .spec file for the server.
-class TypeKeywordMissing : public isc::Exception {
-public:
- TypeKeywordMissing(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
/// @brief Parse Lease Database Parameters
///
/// This class is the parser for the lease database configuration. This is a
@@ -35,7 +25,7 @@ public:
///
/// Only the "type" sub-element is mandatory: the remaining sub-elements
/// depend on the database chosen.
-class DbAccessParser: public DhcpConfigParser {
+class DbAccessParser: public isc::data::SimpleParser {
public:
/// @brief Specifies the database type
@@ -52,58 +42,36 @@ public:
/// @brief Constructor
///
- /// @param param_name Name of the parameter under which the database
- /// access details are held.
/// @param db_type Specifies database type (lease or hosts)
- DbAccessParser(const std::string& param_name, DBType db_type);
+ explicit DbAccessParser(DBType db_type);
/// The destructor.
virtual ~DbAccessParser()
{}
- /// @brief Prepare configuration value.
+ /// @brief Parse configuration value.
///
/// Parses the set of strings forming the database access specification and
/// checks that all are OK. In particular it checks:
///
/// - "type" is "memfile", "mysql" or "postgresql"
/// - "lfc-interval" is a number from the range of 0 to 4294967295.
- /// - "timeout" is a number from the range of 0 to 4294967295.
+ /// - "connect-timeout" is a number from the range of 0 to 4294967295.
+ /// - "port" is a number from the range of 0 to 65535.
///
/// Once all has been validated, constructs the database access string
/// expected by the lease manager.
///
- /// @param config_value The configuration value for the "lease-database"
+ /// @param cfg_db The configuration where the access string will be set
+ /// @param database_config The configuration value for the "*-database"
/// identifier.
///
- /// @throw isc::BadValue The 'type' keyword contains an unknown database
- /// type.
- /// @throw isc::dhcp::MissingTypeKeyword The 'type' keyword is missing from
- /// the list of database access keywords.
- virtual void build(isc::data::ConstElementPtr config_value);
-
- /// @brief This method is no-op.
- virtual void commit();
+ /// @throw isc::dhcp::DhcpConfigError The 'type' keyword contains an
+ /// unknown database type or is missing from the list of
+ /// database access keywords.
+ void parse(isc::dhcp::CfgDbAccessPtr& cfg_db,
+ isc::data::ConstElementPtr database_config);
- /// @brief Factory method to create parser
- ///
- /// Creates an instance of this parser.
- ///
- /// @param param_name Name of the parameter used to access the
- /// configuration.
- ///
- /// @return Pointer to a DbAccessParser. The caller is responsible for
- /// destroying the parser after use.
- static DhcpConfigParser* factory(const std::string& param_name) {
- if (param_name == "lease-database") {
- return (new DbAccessParser(param_name, DbAccessParser::LEASE_DB));
- } else if (param_name == "hosts-database") {
- return (new DbAccessParser(param_name, DbAccessParser::HOSTS_DB));
- } else {
- isc_throw(BadValue, "Unexpected parameter name (" << param_name
- << ") passed to DbAccessParser::factory");
- }
- }
protected:
/// @brief Get database access parameters
@@ -118,6 +86,7 @@ protected:
return (values_);
}
+
/// @brief Construct database access string
///
/// Constructs the database access string from the stored parameters.
diff --git a/src/lib/dhcpsrv/parsers/dhcp_config_parser.h b/src/lib/dhcpsrv/parsers/dhcp_config_parser.h
index d0c759b3b6..92e53413e9 100644
--- a/src/lib/dhcpsrv/parsers/dhcp_config_parser.h
+++ b/src/lib/dhcpsrv/parsers/dhcp_config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
#include <exceptions/exceptions.h>
#include <cc/data.h>
+#include <cc/dhcp_config_error.h>
#include <stdint.h>
#include <string>
#include <map>
@@ -16,20 +17,6 @@
namespace isc {
namespace dhcp {
-/// An exception that is thrown if an error occurs while configuring
-/// DHCP server.
-class DhcpConfigError : public isc::Exception {
-public:
-
- /// @brief constructor
- ///
- /// @param file name of the file, where exception occurred
- /// @param line line of the file, where exception occurred
- /// @param what text description of the issue that caused exception
- DhcpConfigError(const char* file, size_t line, const char* what)
- : isc::Exception(file, line, what) {}
-};
-
/// @brief Forward declaration to DhcpConfigParser class.
///
/// It is only needed here to define types that are
diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
index f6aa0f7639..1a91cdb357 100644
--- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,9 +10,12 @@
#include <dhcp/libdhcp++.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/host_reservations_list_parser.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
#include <dhcpsrv/cfg_mac_source.h>
-#include <hooks/hooks_manager.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
@@ -28,67 +31,11 @@
using namespace std;
using namespace isc::asiolink;
using namespace isc::data;
-using namespace isc::hooks;
using namespace isc::util;
namespace isc {
namespace dhcp {
-// *********************** ParserContext *************************
-
-ParserContext::ParserContext(Option::Universe universe):
- boolean_values_(new BooleanStorage()),
- uint32_values_(new Uint32Storage()),
- string_values_(new StringStorage()),
- hooks_libraries_(),
- universe_(universe)
-{
-}
-
-ParserContext::ParserContext(const ParserContext& rhs):
- boolean_values_(),
- uint32_values_(),
- string_values_(),
- hooks_libraries_(),
- universe_(rhs.universe_)
-{
- copyContext(rhs);
-}
-
-ParserContext&
-// The cppcheck version 1.56 doesn't recognize that copyContext
-// copies all context fields.
-// cppcheck-suppress operatorEqVarError
-ParserContext::operator=(const ParserContext& rhs) {
- if (this != &rhs) {
- copyContext(rhs);
- }
-
- return (*this);
-}
-
-void
-ParserContext::copyContext(const ParserContext& ctx) {
- copyContextPointer(ctx.boolean_values_, boolean_values_);
- copyContextPointer(ctx.uint32_values_, uint32_values_);
- copyContextPointer(ctx.string_values_, string_values_);
- copyContextPointer(ctx.hooks_libraries_, hooks_libraries_);
- // Copy universe.
- universe_ = ctx.universe_;
-}
-
-template<typename T>
-void
-ParserContext::copyContextPointer(const boost::shared_ptr<T>& source_ptr,
- boost::shared_ptr<T>& dest_ptr) {
- if (source_ptr) {
- dest_ptr.reset(new T(*source_ptr));
- } else {
- dest_ptr.reset();
- }
-}
-
-
// **************************** DebugParser *************************
DebugParser::DebugParser(const std::string& param_name)
@@ -117,7 +64,7 @@ template<> void ValueParser<bool>::build(isc::data::ConstElementPtr value) {
// Invoke common code for all specializations of build().
buildCommon(value);
// The Config Manager checks if user specified a
- // valid value for a boolean parameter: True or False.
+ // valid value for a boolean parameter: true or false.
// We should have a boolean Element, use value directly
try {
value_ = value->boolValue();
@@ -177,390 +124,55 @@ template <> void ValueParser<std::string>::build(ConstElementPtr value) {
// ******************** MACSourcesListConfigParser *************************
-MACSourcesListConfigParser::
-MACSourcesListConfigParser(const std::string& param_name,
- ParserContextPtr global_context)
- : param_name_(param_name), global_context_(global_context) {
- if (param_name_ != "mac-sources") {
- isc_throw(BadValue, "Internal error. MAC sources configuration "
- "parser called for the wrong parameter: " << param_name);
- }
-}
-
void
-MACSourcesListConfigParser::build(ConstElementPtr value) {
+MACSourcesListConfigParser::parse(CfgMACSource& mac_sources, ConstElementPtr value) {
CfgIface cfg_iface;
uint32_t source = 0;
+ size_t cnt = 0;
// By default, there's only one source defined: ANY.
// If user specified anything, we need to get rid of that default.
- CfgMgr::instance().getStagingCfg()->getMACSources().clear();
+ mac_sources.clear();
BOOST_FOREACH(ConstElementPtr source_elem, value->listValue()) {
std::string source_str = source_elem->stringValue();
try {
source = CfgMACSource::MACSourceFromText(source_str);
- CfgMgr::instance().getStagingCfg()->getMACSources().add(source);
+ mac_sources.add(source);
+ ++cnt;
+ } catch (const InvalidParameter& ex) {
+ isc_throw(DhcpConfigError, "The mac-sources value '" << source_str
+ << "' was specified twice (" << value->getPosition() << ")");
} catch (const std::exception& ex) {
isc_throw(DhcpConfigError, "Failed to convert '"
<< source_str << "' to any recognized MAC source:"
<< ex.what() << " (" << value->getPosition() << ")");
}
}
-}
-void
-MACSourcesListConfigParser::commit() {
- // Nothing to do.
+ if (!cnt) {
+ isc_throw(DhcpConfigError, "If specified, MAC Sources cannot be empty");
+ }
}
// ******************** ControlSocketParser *************************
-ControlSocketParser::ControlSocketParser(const std::string& param_name) {
- if (param_name != "control-socket") {
- isc_throw(BadValue, "Internal error. Control socket parser called "
- " for wrong parameter:" << param_name);
+void ControlSocketParser::parse(SrvConfig& srv_cfg, isc::data::ConstElementPtr value) {
+ if (!value) {
+ isc_throw(DhcpConfigError, "Logic error: specified control-socket is null");
}
-}
-void ControlSocketParser::build(isc::data::ConstElementPtr value) {
if (value->getType() != Element::map) {
- isc_throw(BadValue, "Specified control-socket is expected to be a map"
+ isc_throw(DhcpConfigError, "Specified control-socket is expected to be a map"
", i.e. a structure defined within { }");
}
- CfgMgr::instance().getStagingCfg()->setControlSocketInfo(value);
-}
-
-/// @brief Does nothing.
-void ControlSocketParser::commit() {
- // Nothing to do.
-}
-
-// ******************** HooksLibrariesParser *************************
-
-HooksLibrariesParser::HooksLibrariesParser(const std::string& param_name)
- : libraries_(), changed_(false)
-{
- // Sanity check on the name.
- if (param_name != "hooks-libraries") {
- isc_throw(BadValue, "Internal error. Hooks libraries "
- "parser called for the wrong parameter: " << param_name);
- }
-}
-
-// Parse the configuration. As Kea has not yet implemented parameters, the
-// parsing code only checks that:
-//
-// 1. Each element in the hooks-libraries list is a map
-// 2. The map contains an element "library" whose value is a string: all
-// other elements in the map are ignored.
-void
-HooksLibrariesParser::build(ConstElementPtr value) {
- // Initialize.
- libraries_.clear();
- changed_ = false;
-
- // This is the new syntax. Iterate through it and get each map.
- BOOST_FOREACH(ConstElementPtr library_entry, value->listValue()) {
- ConstElementPtr parameters;
-
- // Is it a map?
- if (library_entry->getType() != Element::map) {
- isc_throw(DhcpConfigError, "hooks library configuration error:"
- " one or more entries in the hooks-libraries list is not"
- " a map (" << library_entry->getPosition() << ")");
- }
-
- // Iterate iterate through each element in the map. We check
- // whether we have found a library element.
- bool lib_found = false;
-
- string libname = "";
-
- // Let's explicitly reset the parameters, so we won't cover old
- // values from the previous loop round.
- parameters.reset();
-
- BOOST_FOREACH(ConfigPair entry_item, library_entry->mapValue()) {
- if (entry_item.first == "library") {
- if (entry_item.second->getType() != Element::string) {
- isc_throw(DhcpConfigError, "hooks library configuration"
- " error: value of 'library' element is not a string"
- " giving the path to a hooks library (" <<
- entry_item.second->getPosition() << ")");
- }
-
- // Get the name of the library and add it to the list after
- // removing quotes.
- libname = (entry_item.second)->stringValue();
-
- // Remove leading/trailing quotes and any leading/trailing
- // spaces.
- boost::erase_all(libname, "\"");
- libname = isc::util::str::trim(libname);
- if (libname.empty()) {
- isc_throw(DhcpConfigError, "hooks library configuration"
- " error: value of 'library' element must not be"
- " blank (" <<
- entry_item.second->getPosition() << ")");
- }
-
- // Note we have found the library name.
- lib_found = true;
- } else {
- // If there are parameters, let's remember them.
- if (entry_item.first == "parameters") {
- parameters = entry_item.second;
- }
- }
- }
- if (! lib_found) {
- isc_throw(DhcpConfigError, "hooks library configuration error:"
- " one or more hooks-libraries elements are missing the"
- " name of the library" <<
- " (" << library_entry->getPosition() << ")");
- }
-
- libraries_.push_back(make_pair(libname, parameters));
- }
-
- // Check if the list of libraries has changed. If not, nothing is done
- // - the command "DhcpN libreload" is required to reload the same
- // libraries (this prevents needless reloads when anything else in the
- // configuration is changed).
-
- // We no longer rely on this. Parameters can change. And even if the
- // parameters stay the same, they could point to files that could
- // change.
- vector<string> current_libraries = HooksManager::getLibraryNames();
- if (current_libraries.empty() && libraries_.empty()) {
- return;
- }
-
- // Library list has changed, validate each of the libraries specified.
- vector<string> lib_names = isc::hooks::extractNames(libraries_);
- vector<string> error_libs = HooksManager::validateLibraries(lib_names);
- if (!error_libs.empty()) {
-
- // Construct the list of libraries in error for the message.
- string error_list = error_libs[0];
- for (size_t i = 1; i < error_libs.size(); ++i) {
- error_list += (string(", ") + error_libs[i]);
- }
- isc_throw(DhcpConfigError, "hooks libraries failed to validate - "
- "library or libraries in error are: " << error_list
- << " (" << value->getPosition() << ")");
- }
-
- // The library list has changed and the libraries are valid, so flag for
- // update when commit() is called.
- changed_ = true;
-}
-
-void
-HooksLibrariesParser::commit() {
- /// Commits the list of libraries to the configuration manager storage if
- /// the list of libraries has changed.
- if (changed_) {
- /// @todo: Delete any stored CalloutHandles before reloading the
- /// libraries
- HooksManager::loadLibraries(libraries_);
- }
-}
-
-// Method for testing
-void
-HooksLibrariesParser::getLibraries(isc::hooks::HookLibsCollection& libraries,
- bool& changed) {
- libraries = libraries_;
- changed = changed_;
-}
-
-// **************************** OptionDataParser *************************
-OptionDataParser::OptionDataParser(const std::string&, const CfgOptionPtr& cfg,
- const uint16_t address_family)
- : boolean_values_(new BooleanStorage()),
- string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
- option_descriptor_(false), cfg_(cfg),
- address_family_(address_family) {
- // If configuration not specified, then it is a global configuration
- // scope.
- if (!cfg_) {
- cfg_ = CfgMgr::instance().getStagingCfg()->getCfgOption();
- }
-}
-
-void
-OptionDataParser::build(ConstElementPtr option_data_entries) {
- BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
- ParserPtr parser;
- if (param.first == "name" || param.first == "data" ||
- param.first == "space") {
- StringParserPtr name_parser(new StringParser(param.first,
- string_values_));
- parser = name_parser;
- } else if (param.first == "code") {
- Uint32ParserPtr code_parser(new Uint32Parser(param.first,
- uint32_values_));
- parser = code_parser;
- } else if (param.first == "csv-format") {
- BooleanParserPtr value_parser(new BooleanParser(param.first,
- boolean_values_));
- parser = value_parser;
- } else {
- isc_throw(DhcpConfigError,
- "option-data parameter not supported: " << param.first
- << " (" << param.second->getPosition() << ")");
- }
-
- parser->build(param.second);
- // Before we can create an option we need to get the data from
- // the child parsers. The only way to do it is to invoke commit
- // on them so as they store the values in appropriate storages
- // that this class provided to them. Note that this will not
- // modify values stored in the global storages so the configuration
- // will remain consistent even parsing fails somewhere further on.
- parser->commit();
- }
-
- // Try to create the option instance.
- createOption(option_data_entries);
-
- if (!option_descriptor_.option_) {
- isc_throw(isc::InvalidOperation,
- "parser logic error: no option has been configured and"
- " thus there is nothing to commit. Has build() been called?");
- }
-
- cfg_->add(option_descriptor_.option_, option_descriptor_.persistent_,
- option_space_);
-}
-
-void
-OptionDataParser::commit() {
- // Does nothing
-}
-
-OptionalValue<uint32_t>
-OptionDataParser::extractCode(ConstElementPtr parent) const {
- uint32_t code;
- try {
- code = uint32_values_->getParam("code");
-
- } catch (const exception&) {
- // The code parameter was not found. Return an unspecified
- // value.
- return (OptionalValue<uint32_t>());
- }
-
- if (code == 0) {
- isc_throw(DhcpConfigError, "option code must not be zero "
- "(" << uint32_values_->getPosition("code", parent) << ")");
-
- } else if (address_family_ == AF_INET &&
- code > std::numeric_limits<uint8_t>::max()) {
- isc_throw(DhcpConfigError, "invalid option code '" << code
- << "', it must not be greater than '"
- << static_cast<int>(std::numeric_limits<uint8_t>::max())
- << "' (" << uint32_values_->getPosition("code", parent)
- << ")");
-
- } else if (address_family_ == AF_INET6 &&
- code > std::numeric_limits<uint16_t>::max()) {
- isc_throw(DhcpConfigError, "invalid option code '" << code
- << "', it must not exceed '"
- << std::numeric_limits<uint16_t>::max()
- << "' (" << uint32_values_->getPosition("code", parent)
- << ")");
-
- }
-
- return (OptionalValue<uint32_t>(code, OptionalValueState(true)));
-}
-
-OptionalValue<std::string>
-OptionDataParser::extractName(ConstElementPtr parent) const {
- std::string name;
- try {
- name = string_values_->getParam("name");
-
- } catch (...) {
- return (OptionalValue<std::string>());
- }
-
- if (name.find(" ") != std::string::npos) {
- isc_throw(DhcpConfigError, "invalid option name '" << name
- << "', space character is not allowed ("
- << string_values_->getPosition("name", parent) << ")");
- }
-
- return (OptionalValue<std::string>(name, OptionalValueState(true)));
-}
-
-std::string
-OptionDataParser::extractData() const {
- std::string data;
- try {
- data = string_values_->getParam("data");
-
- } catch (...) {
- // The "data" parameter was not found. Return an empty value.
- return (data);
- }
-
- return (data);
-}
-
-OptionalValue<bool>
-OptionDataParser::extractCSVFormat() const {
- bool csv_format = true;
- try {
- csv_format = boolean_values_->getParam("csv-format");
-
- } catch (...) {
- return (OptionalValue<bool>(csv_format));
- }
-
- return (OptionalValue<bool>(csv_format, OptionalValueState(true)));
+ srv_cfg.setControlSocketInfo(value);
}
-std::string
-OptionDataParser::extractSpace() const {
- std::string space = address_family_ == AF_INET ?
- DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE;
- try {
- space = string_values_->getParam("space");
- } catch (...) {
- return (space);
- }
- try {
- if (!OptionSpace::validateName(space)) {
- isc_throw(DhcpConfigError, "invalid option space name '"
- << space << "'");
- }
- if ((space == DHCP4_OPTION_SPACE) && (address_family_ == AF_INET6)) {
- isc_throw(DhcpConfigError, "'" << DHCP4_OPTION_SPACE
- << "' option space name is reserved for DHCPv4 server");
- } else if ((space == DHCP6_OPTION_SPACE) &&
- (address_family_ == AF_INET)) {
- isc_throw(DhcpConfigError, "'" << DHCP6_OPTION_SPACE
- << "' option space name is reserved for DHCPv6 server");
- }
- } catch (std::exception& ex) {
- // Append position of the option space parameter. Note, that in the case
- // when 'space' was not specified a default value will be used and we
- // should never get here. Therefore, it is ok to call getPosition for
- // the space parameter here as this parameter will always be specified.
- isc_throw(DhcpConfigError, ex.what() << " ("
- << string_values_->getPosition("space") << ")");
- }
-
- return (space);
-}
template<typename SearchKey>
OptionDefinitionPtr
@@ -588,260 +200,27 @@ OptionDataParser::findOptionDefinition(const std::string& option_space,
return (def);
}
-void
-OptionDataParser::createOption(ConstElementPtr option_data) {
- const Option::Universe universe = address_family_ == AF_INET ?
- Option::V4 : Option::V6;
-
- OptionalValue<uint32_t> code_param = extractCode(option_data);
- OptionalValue<std::string> name_param = extractName(option_data);
- OptionalValue<bool> csv_format_param = extractCSVFormat();
- std::string data_param = extractData();
- std::string space_param = extractSpace();
-
- // Require that option code or option name is specified.
- if (!code_param.isSpecified() && !name_param.isSpecified()) {
- isc_throw(DhcpConfigError, "option data configuration requires one of"
- " 'code' or 'name' parameters to be specified"
- << " (" << option_data->getPosition() << ")");
- }
-
- // Try to find a corresponding option definition using option code or
- // option name.
- OptionDefinitionPtr def = code_param.isSpecified() ?
- findOptionDefinition(space_param, code_param) :
- findOptionDefinition(space_param, name_param);
-
- // If there is no definition, the user must not explicitly enable the
- // use of csv-format.
- if (!def) {
- // If explicitly requested that the CSV format is to be used,
- // the option definition is a must.
- if (csv_format_param.isSpecified() && csv_format_param) {
- isc_throw(DhcpConfigError, "definition for the option '"
- << space_param << "." << name_param
- << "' having code '" << code_param
- << "' does not exist ("
- << string_values_->getPosition("name", option_data)
- << ")");
-
- // If there is no option definition and the option code is not specified
- // we have no means to find the option code.
- } else if (name_param.isSpecified() && !code_param.isSpecified()) {
- isc_throw(DhcpConfigError, "definition for the option '"
- << space_param << "." << name_param
- << "' does not exist ("
- << string_values_->getPosition("name", option_data)
- << ")");
- }
- }
-
- // Transform string of hexadecimal digits into binary format.
- std::vector<uint8_t> binary;
- std::vector<std::string> data_tokens;
-
- // If the definition is available and csv-format hasn't been explicitly
- // disabled, we will parse the data as comma separated values.
- if (def && (!csv_format_param.isSpecified() || csv_format_param)) {
- // If the option data is specified as a string of comma
- // separated values then we need to split this string into
- // individual values - each value will be used to initialize
- // one data field of an option.
- data_tokens = isc::util::str::tokens(data_param, ",");
-
- } else {
- // Otherwise, the option data is specified as a string of
- // hexadecimal digits that we have to turn into binary format.
- try {
- // The decodeHex function expects that the string contains an
- // even number of digits. If we don't meet this requirement,
- // we have to insert a leading 0.
- if (!data_param.empty() && ((data_param.length() % 2) != 0)) {
- data_param = data_param.insert(0, "0");
- }
- util::encode::decodeHex(data_param, binary);
- } catch (...) {
- isc_throw(DhcpConfigError, "option data is not a valid"
- << " string of hexadecimal digits: " << data_param
- << " ("
- << string_values_->getPosition("data", option_data)
- << ")");
- }
- }
-
- OptionPtr option;
- if (!def) {
- // @todo We have a limited set of option definitions initalized at
- // the moment. In the future we want to initialize option definitions
- // for all options. Consequently an error will be issued if an option
- // definition does not exist for a particular option code. For now it is
- // ok to create generic option if definition does not exist.
- OptionPtr option(new Option(universe, static_cast<uint16_t>(code_param),
- binary));
- // The created option is stored in option_descriptor_ class member
- // until the commit stage when it is inserted into the main storage.
- // If an option with the same code exists in main storage already the
- // old option is replaced.
- option_descriptor_.option_ = option;
- option_descriptor_.persistent_ = false;
-
- } else {
-
- // Option name is specified it should match the name in the definition.
- if (name_param.isSpecified() && (def->getName() != name_param.get())) {
- isc_throw(DhcpConfigError, "specified option name '"
- << name_param << "' does not match the "
- << "option definition: '" << space_param
- << "." << def->getName() << "' ("
- << string_values_->getPosition("name", option_data)
- << ")");
- }
-
- // Option definition has been found so let's use it to create
- // an instance of our option.
- try {
- OptionPtr option =
- !csv_format_param.isSpecified() || csv_format_param ?
- def->optionFactory(universe, def->getCode(), data_tokens) :
- def->optionFactory(universe, def->getCode(), binary);
- OptionDescriptor desc(option, false);
- option_descriptor_.option_ = option;
- option_descriptor_.persistent_ = false;
-
- } catch (const isc::Exception& ex) {
- isc_throw(DhcpConfigError, "option data does not match"
- << " option definition (space: " << space_param
- << ", code: " << def->getCode() << "): "
- << ex.what() << " ("
- << string_values_->getPosition("data", option_data)
- << ")");
- }
- }
-
- // All went good, so we can set the option space name.
- option_space_ = space_param;
-}
-
-// **************************** OptionDataListParser *************************
-OptionDataListParser::OptionDataListParser(const std::string&,
- const CfgOptionPtr& cfg,
- const uint16_t address_family)
- : cfg_(cfg), address_family_(address_family) {
-}
-
-void
-OptionDataListParser::build(ConstElementPtr option_data_list) {
- BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
- boost::shared_ptr<OptionDataParser>
- parser(new OptionDataParser("option-data", cfg_, address_family_));
-
- parser->build(option_value);
- parsers_.push_back(parser);
- }
-}
-
-void
-OptionDataListParser::commit() {
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
- }
- // Append suboptions to the top-level options
- if (cfg_) {
- cfg_->encapsulate();
- } else {
- CfgMgr::instance().getStagingCfg()->getCfgOption()->encapsulate();
- }
-}
-
// ******************************** OptionDefParser ****************************
-OptionDefParser::OptionDefParser(const std::string&,
- ParserContextPtr global_context)
- : boolean_values_(new BooleanStorage()),
- string_values_(new StringStorage()),
- uint32_values_(new Uint32Storage()),
- global_context_(global_context) {
-}
-
-void
-OptionDefParser::build(ConstElementPtr option_def) {
- // Parse the elements that make up the option definition.
- BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
- std::string entry(param.first);
- ParserPtr parser;
- if (entry == "name" || entry == "type" || entry == "record-types"
- || entry == "space" || entry == "encapsulate") {
- StringParserPtr str_parser(new StringParser(entry,
- string_values_));
- parser = str_parser;
- } else if (entry == "code") {
- Uint32ParserPtr code_parser(new Uint32Parser(entry,
- uint32_values_));
- parser = code_parser;
- } else if (entry == "array") {
- BooleanParserPtr array_parser(new BooleanParser(entry,
- boolean_values_));
- parser = array_parser;
- } else {
- isc_throw(DhcpConfigError, "invalid parameter '" << entry
- << "' (" << param.second->getPosition() << ")");
- }
- parser->build(param.second);
- parser->commit();
- }
- // Create an instance of option definition.
- createOptionDef(option_def);
+std::pair<isc::dhcp::OptionDefinitionPtr, std::string>
+OptionDefParser::parse(ConstElementPtr option_def) {
- try {
- CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->
- add(option_definition_, option_space_name_);
+ // Get mandatory parameters.
+ std::string name = getString(option_def, "name");
+ uint32_t code = getInteger(option_def, "code");
+ std::string type = getString(option_def, "type");
- } catch (const std::exception& ex) {
- // Append position if there is a failure.
- isc_throw(DhcpConfigError, ex.what() << " ("
- << option_def->getPosition() << ")");
- }
-
- // All definitions have been prepared. Put them as runtime options into
- // the libdhcp++.
- const OptionDefSpaceContainer& container =
- CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->getContainer();
- LibDHCP::setRuntimeOptionDefs(container);
-}
-
-void
-OptionDefParser::commit() {
- // Do nothing.
-}
-
-void
-OptionDefParser::createOptionDef(ConstElementPtr option_def_element) {
- // Check if mandatory parameters have been specified.
- std::string name;
- uint32_t code;
- std::string type;
- try {
- name = string_values_->getParam("name");
- code = uint32_values_->getParam("code");
- type = string_values_->getParam("type");
- } catch (const std::exception& ex) {
- isc_throw(DhcpConfigError, ex.what() << " ("
- << option_def_element->getPosition() << ")");
- }
-
- bool array_type = boolean_values_->getOptionalParam("array", false);
- std::string record_types =
- string_values_->getOptionalParam("record-types", "");
- std::string space = string_values_->getOptionalParam("space",
- global_context_->universe_ == Option::V4 ? DHCP4_OPTION_SPACE :
- DHCP6_OPTION_SPACE);
- std::string encapsulates =
- string_values_->getOptionalParam("encapsulate", "");
+ // Get optional parameters. Whoever called this parser, should have
+ // called SimpleParser::setDefaults first.
+ bool array_type = getBoolean(option_def, "array");
+ std::string record_types = getString(option_def, "record-types");
+ std::string space = getString(option_def, "space");
+ std::string encapsulates = getString(option_def, "encapsulate");
if (!OptionSpace::validateName(space)) {
isc_throw(DhcpConfigError, "invalid option space name '"
<< space << "' ("
- << string_values_->getPosition("space") << ")");
+ << getPosition("space", option_def) << ")");
}
// Create option definition.
@@ -854,14 +233,14 @@ OptionDefParser::createOptionDef(ConstElementPtr option_def_element) {
isc_throw(DhcpConfigError, "option '" << space << "."
<< "name" << "', comprising an array of data"
<< " fields may not encapsulate any option space ("
- << option_def_element->getPosition() << ")");
+ << option_def->getPosition() << ")");
} else if (encapsulates == space) {
isc_throw(DhcpConfigError, "option must not encapsulate"
<< " an option space it belongs to: '"
<< space << "." << name << "' is set to"
<< " encapsulate '" << space << "' ("
- << option_def_element->getPosition() << ")");
+ << option_def->getPosition() << ")");
} else {
def.reset(new OptionDefinition(name, code, type,
@@ -888,7 +267,7 @@ OptionDefParser::createOptionDef(ConstElementPtr option_def_element) {
isc_throw(DhcpConfigError, "invalid record type values"
<< " specified for the option definition: "
<< ex.what() << " ("
- << string_values_->getPosition("record-types") << ")");
+ << getPosition("record-types", option_def) << ")");
}
}
@@ -897,163 +276,78 @@ OptionDefParser::createOptionDef(ConstElementPtr option_def_element) {
def->validate();
} catch (const std::exception& ex) {
isc_throw(DhcpConfigError, ex.what()
- << " (" << option_def_element->getPosition() << ")");
+ << " (" << option_def->getPosition() << ")");
}
// Option definition has been created successfully.
- option_space_name_ = space;
- option_definition_ = def;
+ return make_pair(def, space);
}
// ******************************** OptionDefListParser ************************
-OptionDefListParser::OptionDefListParser(const std::string&,
- ParserContextPtr global_context)
- : global_context_(global_context) {
-}
-
void
-OptionDefListParser::build(ConstElementPtr option_def_list) {
+OptionDefListParser::parse(CfgOptionDefPtr storage, ConstElementPtr option_def_list) {
if (!option_def_list) {
isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
<< " option definitions is NULL ("
<< option_def_list->getPosition() << ")");
}
+ OptionDefParser parser;
BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
- boost::shared_ptr<OptionDefParser>
- parser(new OptionDefParser("single-option-def", global_context_));
- parser->build(option_def);
+ OptionDefinitionTuple def;
+
+ def = parser.parse(option_def);
+ try {
+ storage->add(def.first, def.second);
+ } catch (const std::exception& ex) {
+ // Append position if there is a failure.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << option_def->getPosition() << ")");
+ }
}
-}
-void
-OptionDefListParser::commit() {
- // Do nothing.
+ // All definitions have been prepared. Put them as runtime options into
+ // the libdhcp++.
+ LibDHCP::setRuntimeOptionDefs(storage->getContainer());
}
//****************************** RelayInfoParser ********************************
-RelayInfoParser::RelayInfoParser(const std::string&,
- const isc::dhcp::Subnet::RelayInfoPtr& relay_info,
- const Option::Universe& family)
- :storage_(relay_info), local_(isc::asiolink::IOAddress(
- family == Option::V4 ? "0.0.0.0" : "::")),
- string_values_(new StringStorage()), family_(family) {
- if (!relay_info) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
- << "relay-info storage may not be NULL");
- }
-
+RelayInfoParser::RelayInfoParser(const Option::Universe& family)
+ : family_(family) {
};
void
-RelayInfoParser::build(ConstElementPtr relay_info) {
-
- BOOST_FOREACH(ConfigPair param, relay_info->mapValue()) {
- ParserPtr parser(createConfigParser(param.first));
- parser->build(param.second);
- parser->commit();
- }
-
- // Get the IP address
- boost::scoped_ptr<asiolink::IOAddress> ip;
- try {
- ip.reset(new asiolink::IOAddress(string_values_->getParam("ip-address")));
- } catch (...) {
- isc_throw(DhcpConfigError, "Failed to parse ip-address "
- "value: " << string_values_->getParam("ip-address")
- << " (" << string_values_->getPosition("ip-address") << ")");
- }
-
- if ( (ip->isV4() && family_ != Option::V4) ||
- (ip->isV6() && family_ != Option::V6) ) {
- isc_throw(DhcpConfigError, "ip-address field " << ip->toText()
+RelayInfoParser::parse(const isc::dhcp::Subnet::RelayInfoPtr& cfg,
+ ConstElementPtr relay_info) {
+ // There is only one parameter which is mandatory
+ IOAddress ip = getAddress(relay_info, "ip-address");
+
+ // Check if the address family matches.
+ if ((ip.isV4() && family_ != Option::V4) ||
+ (ip.isV6() && family_ != Option::V6) ) {
+ isc_throw(DhcpConfigError, "ip-address field " << ip.toText()
<< " does not have IP address of expected family type: "
<< (family_ == Option::V4 ? "IPv4" : "IPv6")
- << " (" << string_values_->getPosition("ip-address") << ")");
- }
-
- local_.addr_ = *ip;
-}
-
-isc::dhcp::ParserPtr
-RelayInfoParser::createConfigParser(const std::string& parameter) {
- DhcpConfigParser* parser = NULL;
- if (parameter.compare("ip-address") == 0) {
- parser = new StringParser(parameter, string_values_);
- } else {
- isc_throw(NotImplemented,
- "parser error: RelayInfoParser parameter not supported: "
- << parameter);
- }
-
- return (isc::dhcp::ParserPtr(parser));
-}
-
-void
-RelayInfoParser::commit() {
- *storage_ = local_;
-}
-
-//****************************** PoolsListParser ********************************
-PoolsListParser::PoolsListParser(const std::string&, PoolStoragePtr pools)
- :pools_(pools), local_pools_(new PoolStorage()) {
- if (!pools_) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
- << "storage may not be NULL");
+ << " (" << getPosition("ip-address", relay_info) << ")");
}
-}
-
-void
-PoolsListParser::build(ConstElementPtr pools) {
- BOOST_FOREACH(ConstElementPtr pool, pools->listValue()) {
- // Iterate over every structure on the pools list and invoke
- // a separate parser for it.
- ParserPtr parser = poolParserMaker(local_pools_);
-
- parser->build(pool);
-
- // Let's store the parser, but do not commit anything yet
- parsers_.push_back(parser);
- }
-}
-
-void PoolsListParser::commit() {
-
- // Commit each parser first. It will store the pool structure
- // in pools_.
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
- }
-
- if (pools_) {
- // local_pools_ holds the values produced by the build function.
- // At this point parsing should have completed successfuly so
- // we can append new data to the supplied storage.
- pools_->insert(pools_->end(), local_pools_->begin(), local_pools_->end());
- }
+ // Ok, we're done with parsing. Let's store the result in the structure
+ // we were given as configuration storage.
+ *cfg = isc::dhcp::Subnet::RelayInfo(ip);
}
//****************************** PoolParser ********************************
-PoolParser::PoolParser(const std::string&, PoolStoragePtr pools,
- const uint16_t address_family)
- :pools_(pools), options_(new CfgOption()),
- address_family_(address_family) {
-
- if (!pools_) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
- << "storage may not be NULL");
- }
-}
void
-PoolParser::build(ConstElementPtr pool_structure) {
+PoolParser::parse(PoolStoragePtr pools,
+ ConstElementPtr pool_structure,
+ const uint16_t address_family) {
ConstElementPtr text_pool = pool_structure->get("pool");
if (!text_pool) {
isc_throw(DhcpConfigError, "Mandatory 'pool' entry missing in "
- "definition: (" << text_pool->getPosition() << ")");
+ "definition: (" << pool_structure->getPosition() << ")");
}
// That should be a single pool representation. It should contain
@@ -1085,38 +379,54 @@ PoolParser::build(ConstElementPtr pool_structure) {
// digits (because there are extra characters left over).
// No checks for values over 128. Range correctness will
- // be checked in Pool4 constructor.
- len = boost::lexical_cast<int>(prefix_len);
+ // be checked in Pool4 constructor, here we only check
+ // the representation fits in an uint8_t as this can't
+ // be done by a direct lexical cast as explained...
+ int val_len = boost::lexical_cast<int>(prefix_len);
+ if ((val_len < std::numeric_limits<uint8_t>::min()) ||
+ (val_len > std::numeric_limits<uint8_t>::max())) {
+ // This exception will be handled 4 line later!
+ isc_throw(OutOfRange, "");
+ }
+ len = static_cast<uint8_t>(val_len);
} catch (...) {
isc_throw(DhcpConfigError, "Failed to parse pool "
- "definition: " << text_pool->stringValue()
- << " (" << text_pool->getPosition() << ")");
+ "definition: " << txt << " ("
+ << text_pool->getPosition() << ")");
}
- pool = poolMaker(addr, len);
- local_pools_.push_back(pool);
-
- // If there's user-context specified, store it.
- ConstElementPtr user_context = pool_structure->get("user-context");
- if (user_context) {
- if (user_context->getType() != Element::map) {
- isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
- << user_context->getPosition() << ")");
- }
- pool->setUserContext(user_context);
+ try {
+ pool = poolMaker(addr, len);
+ pools->push_back(pool);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "Failed to create pool defined by: "
+ << txt << " (" << text_pool->getPosition() << ")");
}
} else {
+ isc::asiolink::IOAddress min("::");
+ isc::asiolink::IOAddress max("::");
// Is this min-max notation?
pos = txt.find("-");
if (pos != string::npos) {
// using min-max notation
- isc::asiolink::IOAddress min(txt.substr(0,pos));
- isc::asiolink::IOAddress max(txt.substr(pos + 1));
+ try {
+ min = isc::asiolink::IOAddress(txt.substr(0, pos));
+ max = isc::asiolink::IOAddress(txt.substr(pos + 1));
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Failed to parse pool "
+ "definition: " << txt << " ("
+ << text_pool->getPosition() << ")");
+ }
- pool = poolMaker(min, max);
- local_pools_.push_back(pool);
+ try {
+ pool = poolMaker(min, max);
+ pools->push_back(pool);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "Failed to create pool defined by: "
+ << txt << " (" << text_pool->getPosition() << ")");
+ }
}
}
@@ -1128,23 +438,23 @@ PoolParser::build(ConstElementPtr pool_structure) {
<< text_pool->getPosition() << ")");
}
+ // If there's user-context specified, store it.
+ ConstElementPtr user_context = pool_structure->get("user-context");
+ if (user_context) {
+ if (user_context->getType() != Element::map) {
+ isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
+ << user_context->getPosition() << ")");
+ }
+ pool->setUserContext(user_context);
+ }
+
// Parser pool specific options.
ConstElementPtr option_data = pool_structure->get("option-data");
if (option_data) {
try {
- // Currently we don't support specifying options for the DHCPv4 server.
- if (address_family_ == AF_INET) {
- isc_throw(DhcpConfigError, "option-data is not supported for DHCPv4"
- " address pools");
- }
-
- OptionDataListParserPtr option_parser(new OptionDataListParser("option-data",
- options_,
- address_family_));
- option_parser->build(option_data);
- option_parser->commit();
- options_->copyTo(*pool->getCfgOption());;
-
+ CfgOptionPtr cfg = pool->getCfgOption();
+ OptionDataListParser option_parser(address_family);
+ option_parser.parse(cfg, option_data);
} catch (const std::exception& ex) {
isc_throw(isc::dhcp::DhcpConfigError, ex.what()
<< " (" << option_data->getPosition() << ")");
@@ -1152,83 +462,63 @@ PoolParser::build(ConstElementPtr pool_structure) {
}
}
+//****************************** Pool4Parser *************************
+
+PoolPtr
+Pool4Parser::poolMaker (IOAddress &addr, uint32_t len, int32_t) {
+ return (PoolPtr(new Pool4(addr, len)));
+}
+
+PoolPtr
+Pool4Parser::poolMaker (IOAddress &min, IOAddress &max, int32_t) {
+ return (PoolPtr(new Pool4(min, max)));
+}
+
+//****************************** Pool4ListParser *************************
+
void
-PoolParser::commit() {
- if (pools_) {
- // local_pools_ holds the values produced by the build function.
- // At this point parsing should have completed successfuly so
- // we can append new data to the supplied storage.
- pools_->insert(pools_->end(), local_pools_.begin(), local_pools_.end());
+Pools4ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list) {
+ BOOST_FOREACH(ConstElementPtr pool, pools_list->listValue()) {
+ Pool4Parser parser;
+ parser.parse(pools, pool, AF_INET);
}
}
//****************************** SubnetConfigParser *************************
-SubnetConfigParser::SubnetConfigParser(const std::string&,
- ParserContextPtr global_context,
- const isc::asiolink::IOAddress& default_addr)
- : uint32_values_(new Uint32Storage()),
- string_values_(new StringStorage()),
- boolean_values_(new BooleanStorage()),
- pools_(new PoolStorage()),
- global_context_(global_context),
- relay_info_(new isc::dhcp::Subnet::RelayInfo(default_addr)),
+SubnetConfigParser::SubnetConfigParser(uint16_t family)
+ : pools_(new PoolStorage()),
+ address_family_(family),
options_(new CfgOption()) {
- // The first parameter should always be "subnet", but we don't check
- // against that here in case some wants to reuse this parser somewhere.
- if (!global_context_) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
- << "context storage may not be NULL");
- }
-
+ string addr = family == AF_INET ? "0.0.0.0" : "::";
+ relay_info_.reset(new isc::dhcp::Subnet::RelayInfo(IOAddress(addr)));
}
-void
-SubnetConfigParser::build(ConstElementPtr subnet) {
- BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
- // Host reservations must be parsed after subnet specific parameters.
- // Note that the reservation parsing will be invoked by the build()
- // in the derived classes, i.e. Subnet4ConfigParser and
- // Subnet6ConfigParser.
- if (param.first == "reservations") {
- continue;
- }
-
- ParserPtr parser;
- // When unsupported parameter is specified, the function called
- // below will thrown an exception. We have to catch this exception
- // to append the line number where the parameter is.
- try {
- parser.reset(createSubnetConfigParser(param.first));
+SubnetPtr
+SubnetConfigParser::parse(ConstElementPtr subnet) {
- } catch (const std::exception& ex) {
- isc_throw(DhcpConfigError, ex.what() << " ("
- << param.second->getPosition() << ")");
- }
- parser->build(param.second);
- parsers_.push_back(parser);
+ ConstElementPtr options_params = subnet->get("option-data");
+ if (options_params) {
+ OptionDataListParser opt_parser(address_family_);
+ opt_parser.parse(options_, options_params);
}
- // In order to create new subnet we need to get the data out
- // of the child parsers first. The only way to do it is to
- // invoke commit on them because it will make them write
- // parsed data into storages we have supplied.
- // Note that triggering commits on child parsers does not
- // affect global data because we supplied pointers to storages
- // local to this object. Thus, even if this method fails
- // later on, the configuration remains consistent.
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
+ ConstElementPtr relay_params = subnet->get("relay");
+ if (relay_params) {
+ Option::Universe u = (address_family_ == AF_INET) ? Option::V4 : Option::V6;
+ RelayInfoParser parser(u);
+ parser.parse(relay_info_, relay_params);
}
// Create a subnet.
try {
- createSubnet();
+ createSubnet(subnet);
} catch (const std::exception& ex) {
isc_throw(DhcpConfigError,
- "subnet configuration failed (" << subnet->getPosition()
- << "): " << ex.what());
+ "subnet configuration failed: " << ex.what());
}
+
+ return (subnet_);
}
Subnet::HRMode
@@ -1246,15 +536,15 @@ SubnetConfigParser::hrModeFromText(const std::string& txt) {
}
void
-SubnetConfigParser::createSubnet() {
+SubnetConfigParser::createSubnet(ConstElementPtr params) {
std::string subnet_txt;
try {
- subnet_txt = string_values_->getParam("subnet");
+ subnet_txt = getString(params, "subnet");
} catch (const DhcpConfigError &) {
// rethrow with precise error
isc_throw(DhcpConfigError,
"mandatory 'subnet' parameter is missing for a subnet being"
- " configured");
+ " configured (" << params->getPosition() << ")");
}
// Remove any spaces or tabs.
@@ -1268,9 +558,10 @@ SubnetConfigParser::createSubnet() {
// need to get all characters preceding "/".
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
+ ConstElementPtr elem = params->get("subnet");
isc_throw(DhcpConfigError,
"Invalid subnet syntax (prefix/len expected):" << subnet_txt
- << " (" << string_values_->getPosition("subnet") << ")");
+ << " (" << elem->getPosition() << ")");
}
// Try to create the address object. It also validates that
@@ -1279,47 +570,53 @@ SubnetConfigParser::createSubnet() {
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
// Call the subclass's method to instantiate the subnet
- initSubnet(addr, len);
+ initSubnet(params, addr, len);
// Add pools to it.
for (PoolStorage::iterator it = pools_->begin(); it != pools_->end();
++it) {
- subnet_->addPool(*it);
+ try {
+ subnet_->addPool(*it);
+ } catch (const BadValue& ex) {
+ // addPool() can throw BadValue if the pool is overlapping or
+ // is out of bounds for the subnet.
+ isc_throw(DhcpConfigError,
+ ex.what() << " (" << params->getPosition() << ")");
+ }
}
- // Configure interface, if defined
+ // Now configure parameters that are common for v4 and v6:
// Get interface name. If it is defined, then the subnet is available
// directly over specified network interface.
- std::string iface;
- try {
- iface = string_values_->getParam("interface");
- } catch (const DhcpConfigError &) {
- // iface not mandatory so swallow the exception
- }
+ std::string iface = getString(params, "interface");
+ if (!iface.empty()) {
+ if (!IfaceMgr::instance().getIface(iface)) {
+ ConstElementPtr error = params->get("interface");
+ isc_throw(DhcpConfigError, "Specified network interface name " << iface
+ << " for subnet " << subnet_->toText()
+ << " is not present in the system ("
+ << error->getPosition() << ")");
+ }
+ subnet_->setIface(iface);
+ }
// Let's set host reservation mode. If not specified, the default value of
// all will be used.
- std::string hr_mode;
try {
- hr_mode = string_values_->getOptionalParam("reservation-mode", "all");
+ std::string hr_mode = getString(params, "reservation-mode");
subnet_->setHostReservationMode(hrModeFromText(hr_mode));
- } catch (const BadValue& ex) {
- isc_throw(DhcpConfigError, "Failed to process specified value "
+ } catch (const BadValue& ex) {
+ isc_throw(DhcpConfigError, "Failed to process specified value "
" of reservation-mode parameter: " << ex.what()
- << string_values_->getPosition("reservation-mode"));
+ << "(" << getPosition("reservation-mode", params) << ")");
}
- if (!iface.empty()) {
- if (!IfaceMgr::instance().getIface(iface)) {
- isc_throw(DhcpConfigError, "Specified interface name " << iface
- << " for subnet " << subnet_->toText()
- << " is not present" << " in the system ("
- << string_values_->getPosition("interface") << ")");
- }
-
- subnet_->setIface(iface);
+ // Try setting up client class.
+ string client_class = getString(params, "client-class");
+ if (!client_class.empty()) {
+ subnet_->allowClientClass(client_class);
}
// Here globally defined options were merged to the subnet specific
@@ -1330,225 +627,620 @@ SubnetConfigParser::createSubnet() {
options_->copyTo(*subnet_->getCfgOption());
}
-isc::dhcp::Triplet<uint32_t>
-SubnetConfigParser::getParam(const std::string& name) {
- uint32_t value = 0;
+//****************************** Subnet4ConfigParser *************************
+
+Subnet4ConfigParser::Subnet4ConfigParser()
+ :SubnetConfigParser(AF_INET) {
+}
+
+Subnet4Ptr
+Subnet4ConfigParser::parse(ConstElementPtr subnet) {
+ /// Parse Pools first.
+ ConstElementPtr pools = subnet->get("pools");
+ if (pools) {
+ Pools4ListParser parser;
+ parser.parse(pools_, pools);
+ }
+
+ SubnetPtr generic = SubnetConfigParser::parse(subnet);
+
+ if (!generic) {
+ isc_throw(DhcpConfigError,
+ "Failed to create an IPv4 subnet (" <<
+ subnet->getPosition() << ")");
+ }
+
+ Subnet4Ptr sn4ptr = boost::dynamic_pointer_cast<Subnet4>(subnet_);
+ if (!sn4ptr) {
+ // If we hit this, it is a programming error.
+ isc_throw(Unexpected,
+ "Invalid Subnet4 cast in Subnet4ConfigParser::parse");
+ }
+
+ // Set relay information if it was parsed
+ if (relay_info_) {
+ sn4ptr->setRelayInfo(*relay_info_);
+ }
+
+ // Parse Host Reservations for this subnet if any.
+ ConstElementPtr reservations = subnet->get("reservations");
+ if (reservations) {
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser4> parser;
+ parser.parse(subnet_->getID(), reservations, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+ }
+
+ return (sn4ptr);
+}
+
+void
+Subnet4ConfigParser::initSubnet(data::ConstElementPtr params,
+ asiolink::IOAddress addr, uint8_t len) {
+ // The renew-timer and rebind-timer are optional. If not set, the
+ // option 58 and 59 will not be sent to a client. In this case the
+ // client will use default values based on the valid-lifetime.
+ Triplet<uint32_t> t1 = getInteger(params, "renew-timer");
+ Triplet<uint32_t> t2 = getInteger(params, "rebind-timer");
+
+ // The valid-lifetime is mandatory. It may be specified for a
+ // particular subnet. If not, the global value should be present.
+ // If there is no global value, exception is thrown.
+ Triplet<uint32_t> valid = getInteger(params, "valid-lifetime");
+
+ // Subnet ID is optional. If it is not supplied the value of 0 is used,
+ // which means autogenerate. The value was inserted earlier by calling
+ // SimpleParser4::setAllDefaults.
+ SubnetID subnet_id = static_cast<SubnetID>(getInteger(params, "id"));
+
+ stringstream s;
+ s << addr << "/" << static_cast<int>(len) << " with params: ";
+ // t1 and t2 are optional may be not specified.
+ if (!t1.unspecified()) {
+ s << "t1=" << t1 << ", ";
+ }
+ if (!t2.unspecified()) {
+ s << "t2=" << t2 << ", ";
+ }
+ s <<"valid-lifetime=" << valid;
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_NEW_SUBNET4).arg(s.str());
+
+ Subnet4Ptr subnet4(new Subnet4(addr, len, t1, t2, valid, subnet_id));
+ subnet_ = subnet4;
+
+ // Set the match-client-id value for the subnet. It is always present.
+ // If not explicitly specified, the default value was filled in when
+ // SimpleParser4::setAllDefaults was called.
+ bool match_client_id = getBoolean(params, "match-client-id");
+ subnet4->setMatchClientId(match_client_id);
+
+ // Set next-server. The default value is 0.0.0.0. Nevertheless, the
+ // user could have messed that up by specifying incorrect value.
+ // To avoid using 0.0.0.0, user can specify "".
+ string next_server;
try {
- // look for local value
- value = uint32_values_->getParam(name);
- } catch (const DhcpConfigError &) {
+ next_server = getString(params, "next-server");
+ if (!next_server.empty()) {
+ subnet4->setSiaddr(IOAddress(next_server));
+ }
+ } catch (...) {
+ ConstElementPtr next = params->get("next-server");
+ string pos;
+ if (next) {
+ pos = next->getPosition().str();
+ } else {
+ pos = params->getPosition().str();
+ }
+ isc_throw(DhcpConfigError, "invalid parameter next-server : "
+ << next_server << "(" << pos << ")");
+ }
+
+ // 4o6 specific parameter: 4o6-interface. If not explicitly specified,
+ // it will have the default value of "".
+ string iface4o6 = getString(params, "4o6-interface");
+ if (!iface4o6.empty()) {
+ subnet4->get4o6().setIface4o6(iface4o6);
+ subnet4->get4o6().enabled(true);
+ }
+
+ // 4o6 specific parameter: 4o6-subnet. If not explicitly specified, it
+ // will have the default value of "".
+ string subnet4o6 = getString(params, "4o6-subnet");
+ if (!subnet4o6.empty()) {
+ size_t slash = subnet4o6.find("/");
+ if (slash == std::string::npos) {
+ isc_throw(DhcpConfigError, "Missing / in the 4o6-subnet parameter:"
+ << subnet4o6 << ", expected format: prefix6/length");
+ }
+ string prefix = subnet4o6.substr(0, slash);
+ string lenstr = subnet4o6.substr(slash + 1);
+
+ uint8_t len = 128;
try {
- // no local, use global value
- value = global_context_->uint32_values_->getParam(name);
- } catch (const DhcpConfigError &) {
- isc_throw(DhcpConfigError, "Mandatory parameter " << name
- << " missing (no global default and no subnet-"
- << "specific value)");
+ len = boost::lexical_cast<unsigned int>(lenstr.c_str());
+ } catch (const boost::bad_lexical_cast &) {
+ isc_throw(DhcpConfigError, "Invalid prefix length specified in "
+ "4o6-subnet parameter: " << subnet4o6 << ", expected 0..128 value");
+ }
+ subnet4->get4o6().setSubnet4o6(IOAddress(prefix), len);
+ subnet4->get4o6().enabled(true);
+ }
+
+ // Try 4o6 specific parameter: 4o6-interface-id
+ std::string ifaceid = getString(params, "4o6-interface-id");
+ if (!ifaceid.empty()) {
+ OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
+ OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet4->get4o6().setInterfaceId(opt);
+ subnet4->get4o6().enabled(true);
+ }
+
+ /// client-class processing is now generic and handled in the common
+ /// code (see isc::data::SubnetConfigParser::createSubnet)
+}
+
+//**************************** Subnets4ListConfigParser **********************
+
+size_t
+Subnets4ListConfigParser::parse(SrvConfigPtr cfg, ConstElementPtr subnets_list) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+ Subnet4ConfigParser parser;
+ Subnet4Ptr subnet = parser.parse(subnet_json);
+ if (subnet) {
+
+ // Adding a subnet to the Configuration Manager may fail if the
+ // subnet id is invalid (duplicate). Thus, we catch exceptions
+ // here to append a position in the configuration string.
+ try {
+ cfg->getCfgSubnets4()->add(subnet);
+ cnt++;
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << subnet_json->getPosition() << ")");
+ }
}
}
+ return (cnt);
+}
+
+//**************************** Pool6Parser *********************************
+
+PoolPtr
+Pool6Parser::poolMaker (IOAddress &addr, uint32_t len, int32_t ptype)
+{
+ return (PoolPtr(new Pool6(static_cast<isc::dhcp::Lease::Type>
+ (ptype), addr, len)));
+}
+
+PoolPtr
+Pool6Parser::poolMaker (IOAddress &min, IOAddress &max, int32_t ptype)
+{
+ return (PoolPtr(new Pool6(static_cast<isc::dhcp::Lease::Type>
+ (ptype), min, max)));
+}
+
+
+//**************************** Pool6ListParser ***************************
+
+void
+Pools6ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list) {
+ BOOST_FOREACH(ConstElementPtr pool, pools_list->listValue()) {
+ Pool6Parser parser;
+ parser.parse(pools, pool, AF_INET6);
+ }
+}
+
+//**************************** PdPoolParser ******************************
- return (Triplet<uint32_t>(value));
+PdPoolParser::PdPoolParser() : options_(new CfgOption()) {
}
-isc::dhcp::Triplet<uint32_t>
-SubnetConfigParser::getOptionalParam(const std::string& name) {
+void
+PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) {
+ std::string addr_str = getString(pd_pool_, "prefix");
+
+ uint8_t prefix_len = getUint8(pd_pool_, "prefix-len");
+
+ uint8_t delegated_len = getUint8(pd_pool_, "delegated-len");
+
+ std::string excluded_prefix_str = "::";
+ if (pd_pool_->contains("excluded-prefix")) {
+ excluded_prefix_str = getString(pd_pool_, "excluded-prefix");
+ }
+
+ uint8_t excluded_prefix_len = 0;
+ if (pd_pool_->contains("excluded-prefix-len")) {
+ excluded_prefix_len = getUint8(pd_pool_, "excluded-prefix-len");
+ }
+
+ ConstElementPtr option_data = pd_pool_->get("option-data");
+ if (option_data) {
+ OptionDataListParser opts_parser(AF_INET6);
+ opts_parser.parse(options_, option_data);
+ }
+
+ ConstElementPtr user_context = pd_pool_->get("user-context");
+ if (user_context) {
+ user_context_ = user_context;
+ }
+
+ // Check the pool parameters. It will throw an exception if any
+ // of the required parameters are invalid.
try {
- return (getParam(name));
- } catch (const DhcpConfigError &) {
- // No error. We will return an unspecified value.
+ // Attempt to construct the local pool.
+ pool_.reset(new Pool6(IOAddress(addr_str),
+ prefix_len,
+ delegated_len,
+ IOAddress(excluded_prefix_str),
+ excluded_prefix_len));
+ // Merge options specified for a pool into pool configuration.
+ options_->copyTo(*pool_->getCfgOption());
+ } catch (const std::exception& ex) {
+ // Some parameters don't exist or are invalid. Since we are not
+ // aware whether they don't exist or are invalid, let's append
+ // the position of the pool map element.
+ isc_throw(isc::dhcp::DhcpConfigError, ex.what()
+ << " (" << pd_pool_->getPosition() << ")");
+ }
+
+ if (user_context_) {
+ pool_->setUserContext(user_context_);
}
- return (Triplet<uint32_t>());
+
+ // Add the local pool to the external storage ptr.
+ pools->push_back(pool_);
}
-//**************************** D2ClientConfigParser **********************
-D2ClientConfigParser::D2ClientConfigParser(const std::string& entry_name)
- : entry_name_(entry_name), boolean_values_(new BooleanStorage()),
- uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
- local_client_config_() {
+//**************************** PdPoolsListParser ************************
+
+void
+PdPoolsListParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_list) {
+ // Loop through the list of pd pools.
+ BOOST_FOREACH(ConstElementPtr pd_pool, pd_pool_list->listValue()) {
+ PdPoolParser parser;
+ parser.parse(pools, pd_pool);
+ }
+}
+
+//**************************** Subnet6ConfigParser ***********************
+
+Subnet6ConfigParser::Subnet6ConfigParser()
+ :SubnetConfigParser(AF_INET6) {
+}
+
+Subnet6Ptr
+Subnet6ConfigParser::parse(ConstElementPtr subnet) {
+ /// Parse all pools first.
+ ConstElementPtr pools = subnet->get("pools");
+ if (pools) {
+ Pools6ListParser parser;
+ parser.parse(pools_, pools);
+ }
+ ConstElementPtr pd_pools = subnet->get("pd-pools");
+ if (pd_pools) {
+ PdPoolsListParser parser;
+ parser.parse(pools_, pd_pools);
+ }
+
+ SubnetPtr generic = SubnetConfigParser::parse(subnet);
+
+ if (!generic) {
+ isc_throw(DhcpConfigError,
+ "Failed to create an IPv6 subnet (" <<
+ subnet->getPosition() << ")");
+ }
+
+ Subnet6Ptr sn6ptr = boost::dynamic_pointer_cast<Subnet6>(subnet_);
+ if (!sn6ptr) {
+ // If we hit this, it is a programming error.
+ isc_throw(Unexpected,
+ "Invalid Subnet6 cast in Subnet6ConfigParser::parse");
+ }
+
+ // Set relay information if it was provided
+ if (relay_info_) {
+ sn6ptr->setRelayInfo(*relay_info_);
+ }
+
+
+ // Parse Host Reservations for this subnet if any.
+ ConstElementPtr reservations = subnet->get("reservations");
+ if (reservations) {
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser6> parser;
+ parser.parse(subnet_->getID(), reservations, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+ }
+
+ return (sn6ptr);
}
-D2ClientConfigParser::~D2ClientConfigParser() {
+void
+Subnet6ConfigParser::duplicate_option_warning(uint32_t code,
+ asiolink::IOAddress& addr) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_OPTION_DUPLICATE)
+ .arg(code).arg(addr.toText());
}
void
-D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) {
- BOOST_FOREACH(ConfigPair param, client_config->mapValue()) {
- ParserPtr parser;
+Subnet6ConfigParser::initSubnet(data::ConstElementPtr params,
+ asiolink::IOAddress addr, uint8_t len) {
+ // Get all 'time' parameters using inheritance.
+ // If the subnet-specific value is defined then use it, else
+ // use the global value. The global value must always be
+ // present. If it is not, it is an internal error and exception
+ // is thrown.
+ Triplet<uint32_t> t1 = getInteger(params, "renew-timer");
+ Triplet<uint32_t> t2 = getInteger(params, "rebind-timer");
+ Triplet<uint32_t> pref = getInteger(params, "preferred-lifetime");
+ Triplet<uint32_t> valid = getInteger(params, "valid-lifetime");
+
+ // Subnet ID is optional. If it is not supplied the value of 0 is used,
+ // which means autogenerate. The value was inserted earlier by calling
+ // SimpleParser6::setAllDefaults.
+ SubnetID subnet_id = static_cast<SubnetID>(getInteger(params, "id"));
+
+ // We want to log whether rapid-commit is enabled, so we get this
+ // before the actual subnet creation.
+ bool rapid_commit = getBoolean(params, "rapid-commit");
+
+ std::ostringstream output;
+ output << addr << "/" << static_cast<int>(len)
+ << " with params t1=" << t1 << ", t2="
+ << t2 << ", preferred-lifetime=" << pref
+ << ", valid-lifetime=" << valid
+ << ", rapid-commit is " << (rapid_commit ? "enabled" : "disabled");
+
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_NEW_SUBNET4).arg(output.str());
+
+ // Create a new subnet.
+ Subnet6* subnet6 = new Subnet6(addr, len, t1, t2, pref, valid,
+ subnet_id);
+ subnet_.reset(subnet6);
+
+ // Enable or disable Rapid Commit option support for the subnet.
+ subnet6->setRapidCommit(rapid_commit);
+
+ // Get interface-id option content. For now we support string
+ // representation only
+ std::string ifaceid = getString(params, "interface-id");
+ std::string iface = getString(params, "interface");
+
+ // Specifying both interface for locally reachable subnets and
+ // interface id for relays is mutually exclusive. Need to test for
+ // this condition.
+ if (!ifaceid.empty() && !iface.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "parser error: interface (defined for locally reachable "
+ "subnets) and interface-id (defined for subnets reachable"
+ " via relays) cannot be defined at the same time for "
+ "subnet " << addr << "/" << (int)len << "("
+ << params->getPosition() << ")");
+ }
+
+ // Configure interface-id for remote interfaces, if defined
+ if (!ifaceid.empty()) {
+ OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
+ OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet6->setInterfaceId(opt);
+ }
+
+ /// client-class processing is now generic and handled in the common
+ /// code (see isc::data::SubnetConfigParser::createSubnet)
+}
+
+//**************************** Subnet6ListConfigParser ********************
+
+size_t
+Subnets6ListConfigParser::parse(SrvConfigPtr cfg, ConstElementPtr subnets_list) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+ Subnet6ConfigParser parser;
+ Subnet6Ptr subnet = parser.parse(subnet_json);
+
+ // Adding a subnet to the Configuration Manager may fail if the
+ // subnet id is invalid (duplicate). Thus, we catch exceptions
+ // here to append a position in the configuration string.
try {
- parser = createConfigParser(param.first);
- } catch (std::exception& ex) {
- // Catch exception in case the configuration contains the
- // unsupported parameter. In this case, we will need to
- // append the position of this element.
+ cfg->getCfgSubnets6()->add(subnet);
+ cnt++;
+ } catch (const std::exception& ex) {
isc_throw(DhcpConfigError, ex.what() << " ("
- << param.second->getPosition() << ")");
+ << subnet_json->getPosition() << ")");
}
-
- parser->build(param.second);
- parser->commit();
}
+ return (cnt);
+}
- /// @todo Create configuration from the configuration parameters. Because
- /// the validation of the D2 configuration is atomic, there is no way to
- /// tell which parameter is invalid. Therefore, we catch all exceptions
- /// and append the line number of the parent element. In the future we
- /// may should extend D2ClientConfig code so as it returns the name of
- /// the invalid parameter.
- try {
- bool enable_updates = boolean_values_->getParam("enable-updates");
- if (!enable_updates && (client_config->mapValue().size() == 1)) {
- // If enable-updates is the only parameter and it is false then
- // we're done. This allows for an abbreviated configuration entry
- // that only contains that flag. Use the default D2ClientConfig
- // constructor to a create a disabled instance.
- local_client_config_.reset(new D2ClientConfig());
-
- return;
- }
- // Get all parameters that are needed to create the D2ClientConfig.
+//**************************** D2ClientConfigParser **********************
+
+dhcp_ddns::NameChangeProtocol
+D2ClientConfigParser::getProtocol(ConstElementPtr scope,
+ const std::string& name) {
+ return (getAndConvert<dhcp_ddns::NameChangeProtocol,
+ dhcp_ddns::stringToNcrProtocol>
+ (scope, name, "NameChangeRequest protocol"));
+}
+
+dhcp_ddns::NameChangeFormat
+D2ClientConfigParser::getFormat(ConstElementPtr scope,
+ const std::string& name) {
+ return (getAndConvert<dhcp_ddns::NameChangeFormat,
+ dhcp_ddns::stringToNcrFormat>
+ (scope, name, "NameChangeRequest format"));
+}
+
+D2ClientConfig::ReplaceClientNameMode
+D2ClientConfigParser::getMode(ConstElementPtr scope,
+ const std::string& name) {
+ return (getAndConvert<D2ClientConfig::ReplaceClientNameMode,
+ D2ClientConfig::stringToReplaceClientNameMode>
+ (scope, name, "ReplaceClientName mode"));
+}
+
+D2ClientConfigPtr
+D2ClientConfigParser::parse(isc::data::ConstElementPtr client_config) {
+ D2ClientConfigPtr new_config;
+
+ // Get all parameters that are needed to create the D2ClientConfig.
+ bool enable_updates = getBoolean(client_config, "enable-updates");
+
+ IOAddress server_ip = getAddress(client_config, "server-ip");
+
+ uint32_t server_port = getUint32(client_config, "server-port");
+
+ std::string sender_ip_str = getString(client_config, "sender-ip");
+
+ uint32_t sender_port = getUint32(client_config, "sender-port");
+
+ uint32_t max_queue_size = getUint32(client_config, "max-queue-size");
+
+ dhcp_ddns::NameChangeProtocol ncr_protocol =
+ getProtocol(client_config, "ncr-protocol");
+
+ dhcp_ddns::NameChangeFormat ncr_format =
+ getFormat(client_config, "ncr-format");
+
+ bool always_include_fqdn =
+ getBoolean(client_config, "always-include-fqdn");
+
+ bool override_no_update =
+ getBoolean(client_config, "override-no-update");
+
+ bool override_client_update =
+ getBoolean(client_config, "override-client-update");
- // The qualifying suffix is mandatory when updates are enabled
- std::string qualifying_suffix =
- string_values_->getParam("qualifying-suffix");
+ D2ClientConfig::ReplaceClientNameMode replace_client_name_mode =
+ getMode(client_config, "replace-client-name");
- IOAddress server_ip =
- IOAddress(string_values_->getOptionalParam("server-ip",
- D2ClientConfig::
- DFT_SERVER_IP));
+ std::string generated_prefix =
+ getString(client_config, "generated-prefix");
- uint32_t server_port =
- uint32_values_->getOptionalParam("server-port",
- D2ClientConfig::DFT_SERVER_PORT);
+ // qualifying-suffix is the only parameter which has no default
+ std::string qualifying_suffix = "";
+ bool found_qualifying_suffix = false;
+ if (client_config->contains("qualifying-suffix")) {
+ qualifying_suffix = getString(client_config, "qualifying-suffix");
+ found_qualifying_suffix = true;
+ }
+ IOAddress sender_ip(0);
+ if (sender_ip_str.empty()) {
// The default sender IP depends on the server IP family
- asiolink::IOAddress
- sender_ip(string_values_->
- getOptionalParam("sender-ip",
- (server_ip.isV4() ?
- D2ClientConfig::DFT_V4_SENDER_IP :
- D2ClientConfig::DFT_V6_SENDER_IP)));
-
- uint32_t sender_port =
- uint32_values_->getOptionalParam("sender-port",
- D2ClientConfig::
- DFT_SENDER_PORT);
- uint32_t max_queue_size
- = uint32_values_->getOptionalParam("max-queue-size",
- D2ClientConfig::
- DFT_MAX_QUEUE_SIZE);
-
- dhcp_ddns::NameChangeProtocol ncr_protocol =
- dhcp_ddns::stringToNcrProtocol(string_values_->
- getOptionalParam("ncr-protocol",
- D2ClientConfig::
- DFT_NCR_PROTOCOL));
- dhcp_ddns::NameChangeFormat ncr_format
- = dhcp_ddns::stringToNcrFormat(string_values_->
- getOptionalParam("ncr-format",
- D2ClientConfig::
- DFT_NCR_FORMAT));
- std::string generated_prefix =
- string_values_->getOptionalParam("generated-prefix",
- D2ClientConfig::
- DFT_GENERATED_PREFIX);
- bool always_include_fqdn =
- boolean_values_->getOptionalParam("always-include-fqdn",
- D2ClientConfig::
- DFT_ALWAYS_INCLUDE_FQDN);
-
- bool override_no_update =
- boolean_values_->getOptionalParam("override-no-update",
- D2ClientConfig::
- DFT_OVERRIDE_NO_UPDATE);
-
- bool override_client_update =
- boolean_values_->getOptionalParam("override-client-update",
- D2ClientConfig::
- DFT_OVERRIDE_CLIENT_UPDATE);
-
- // Formerly, replace-client-name was boolean, so for now we'll support boolean
- // values by mapping them to the appropriate mode
- D2ClientConfig::ReplaceClientNameMode replace_client_name_mode;
- std::string mode_str;
- mode_str = string_values_->getOptionalParam("replace-client-name",
- D2ClientConfig::
- DFT_REPLACE_CLIENT_NAME_MODE);
- if (boost::iequals(mode_str, "FALSE")) {
- // @todo add a debug log
- replace_client_name_mode = D2ClientConfig::RCM_NEVER;
- }
- else if (boost::iequals(mode_str, "TRUE")) {
- // @todo add a debug log
- replace_client_name_mode = D2ClientConfig::RCM_WHEN_PRESENT;
- } else {
- replace_client_name_mode = D2ClientConfig::
- stringToReplaceClientNameMode(mode_str);
+ sender_ip = (server_ip.isV4() ? IOAddress::IPV4_ZERO_ADDRESS() :
+ IOAddress::IPV6_ZERO_ADDRESS());
+ } else {
+ try {
+ sender_ip = IOAddress(sender_ip_str);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "invalid address (" << sender_ip_str
+ << ") specified for parameter 'sender-ip' ("
+ << getPosition("sender-ip", client_config) << ")");
}
+ }
+
+ // Qualifying-suffix is required when updates are enabled
+ if (enable_updates && !found_qualifying_suffix) {
+ isc_throw(DhcpConfigError,
+ "parameter 'qualifying-suffix' is required when "
+ "updates are enabled ("
+ << client_config->getPosition() << ")");
+ }
+
+ // Now we check for logical errors. This repeats what is done in
+ // D2ClientConfig::validate(), but doing it here permits us to
+ // emit meaningful parameter position info in the error.
+ if (ncr_format != dhcp_ddns::FMT_JSON) {
+ isc_throw(D2ClientError, "D2ClientConfig error: NCR Format: "
+ << dhcp_ddns::ncrFormatToString(ncr_format)
+ << " is not supported. ("
+ << getPosition("ncr-format", client_config) << ")");
+ }
+
+ if (ncr_protocol != dhcp_ddns::NCR_UDP) {
+ isc_throw(D2ClientError, "D2ClientConfig error: NCR Protocol: "
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol)
+ << " is not supported. ("
+ << getPosition("ncr-protocol", client_config) << ")");
+ }
+
+ if (sender_ip.getFamily() != server_ip.getFamily()) {
+ isc_throw(D2ClientError,
+ "D2ClientConfig error: address family mismatch: "
+ << "server-ip: " << server_ip.toText()
+ << " is: " << (server_ip.isV4() ? "IPv4" : "IPv6")
+ << " while sender-ip: " << sender_ip.toText()
+ << " is: " << (sender_ip.isV4() ? "IPv4" : "IPv6")
+ << " (" << getPosition("sender-ip", client_config) << ")");
+ }
+ if (server_ip == sender_ip && server_port == sender_port) {
+ isc_throw(D2ClientError,
+ "D2ClientConfig error: server and sender cannot"
+ " share the exact same IP address/port: "
+ << server_ip.toText() << "/" << server_port
+ << " (" << getPosition("sender-ip", client_config) << ")");
+ }
+
+ try {
// Attempt to create the new client config.
- local_client_config_.reset(new D2ClientConfig(enable_updates,
- server_ip,
- server_port,
- sender_ip,
- sender_port,
- max_queue_size,
- ncr_protocol,
- ncr_format,
- always_include_fqdn,
- override_no_update,
- override_client_update,
- replace_client_name_mode,
- generated_prefix,
- qualifying_suffix));
+ new_config.reset(new D2ClientConfig(enable_updates,
+ server_ip,
+ server_port,
+ sender_ip,
+ sender_port,
+ max_queue_size,
+ ncr_protocol,
+ ncr_format,
+ always_include_fqdn,
+ override_no_update,
+ override_client_update,
+ replace_client_name_mode,
+ generated_prefix,
+ qualifying_suffix));
} catch (const std::exception& ex) {
isc_throw(DhcpConfigError, ex.what() << " ("
<< client_config->getPosition() << ")");
}
-}
-isc::dhcp::ParserPtr
-D2ClientConfigParser::createConfigParser(const std::string& config_id) {
- DhcpConfigParser* parser = NULL;
- if ((config_id.compare("server-port") == 0) ||
- (config_id.compare("sender-port") == 0) ||
- (config_id.compare("max-queue-size") == 0)) {
- parser = new Uint32Parser(config_id, uint32_values_);
- } else if ((config_id.compare("server-ip") == 0) ||
- (config_id.compare("ncr-protocol") == 0) ||
- (config_id.compare("ncr-format") == 0) ||
- (config_id.compare("generated-prefix") == 0) ||
- (config_id.compare("sender-ip") == 0) ||
- (config_id.compare("qualifying-suffix") == 0) ||
- (config_id.compare("replace-client-name") == 0)) {
- parser = new StringParser(config_id, string_values_);
- } else if ((config_id.compare("enable-updates") == 0) ||
- (config_id.compare("always-include-fqdn") == 0) ||
- (config_id.compare("allow-client-update") == 0) ||
- (config_id.compare("override-no-update") == 0) ||
- (config_id.compare("override-client-update") == 0)) {
- parser = new BooleanParser(config_id, boolean_values_);
- } else {
- isc_throw(NotImplemented,
- "parser error: D2ClientConfig parameter not supported: "
- << config_id);
- }
-
- return (isc::dhcp::ParserPtr(parser));
+ return(new_config);
}
-void
-D2ClientConfigParser::commit() {
- // @todo if local_client_config_ is empty then shutdown the listener...
- // @todo Should this also attempt to start a listener?
- // In keeping with Interface, Subnet, and Hooks parsers, then this
- // should initialize the listener. Failure to init it, should cause
- // rollback. This gets sticky, because who owns the listener instance?
- // Does CfgMgr maintain it or does the server class? If the latter
- // how do we get that value here?
- // I'm thinkikng D2ClientConfig could contain the listener instance
- CfgMgr::instance().setD2ClientConfig(local_client_config_);
+/// @brief This table defines default values for D2 client configuration
+const SimpleDefaults D2ClientConfigParser::D2_CLIENT_CONFIG_DEFAULTS = {
+ // enable-updates is unconditionally required
+ { "server-ip", Element::string, "127.0.0.1" },
+ { "server-port", Element::integer, "53001" },
+ // default sender-ip depends on server-ip family, so we leave default blank
+ // parser knows to use the appropriate ZERO address based on server-ip
+ { "sender-ip", Element::string, "" },
+ { "sender-port", Element::integer, "0" },
+ { "max-queue-size", Element::integer, "1024" },
+ { "ncr-protocol", Element::string, "UDP" },
+ { "ncr-format", Element::string, "JSON" },
+ { "always-include-fqdn", Element::boolean, "false" },
+ { "override-no-update", Element::boolean, "false" },
+ { "override-client-update", Element::boolean, "false" },
+ { "replace-client-name", Element::string, "never" },
+ { "generated-prefix", Element::string, "myhost" }
+ // qualifying-suffix has no default
+};
+
+size_t
+D2ClientConfigParser::setAllDefaults(isc::data::ConstElementPtr d2_config) {
+ ElementPtr mutable_d2 = boost::const_pointer_cast<Element>(d2_config);
+ return (SimpleParser::setDefaults(mutable_d2, D2_CLIENT_CONFIG_DEFAULTS));
}
}; // namespace dhcp
diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h
index 67f62074c1..5e03b00d51 100644
--- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h
+++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,8 +15,11 @@
#include <dhcpsrv/cfg_iface.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/cfg_option_def.h>
+#include <dhcpsrv/cfg_mac_source.h>
+#include <dhcpsrv/srv_config.h>
#include <dhcpsrv/parsers/dhcp_config_parser.h>
-#include <hooks/libinfo.h>
+#include <cc/simple_parser.h>
#include <exceptions/exceptions.h>
#include <util/optional_value.h>
@@ -57,7 +60,7 @@ public:
/// otherwise its data value and the position will be updated with the
/// given values.
///
- /// @param name is the name of the paramater to store.
+ /// @param name is the name of the parameter to store.
/// @param value is the data value to store.
/// @param position is the position of the data element within a
/// configuration string (file).
@@ -73,7 +76,7 @@ public:
/// @param name is the name of the parameter for which the data
/// value is desired.
///
- /// @return The paramater's data value of type @c ValueType.
+ /// @return The parameter's data value of type @c ValueType.
/// @throw DhcpConfigError if the parameter is not found.
ValueType getParam(const std::string& name) const {
typename std::map<std::string, ValueType>::const_iterator param
@@ -122,7 +125,7 @@ public:
/// value is desired.
/// @param default_value value to use the default
///
- /// @return The paramater's data value of type @c ValueType.
+ /// @return The parameter's data value of type @c ValueType.
ValueType getOptionalParam(const std::string& name,
const ValueType& default_value) const {
typename std::map<std::string, ValueType>::const_iterator param
@@ -140,7 +143,7 @@ public:
/// Deletes the entry for the given parameter from the store if it
/// exists.
///
- /// @param name is the name of the paramater to delete.
+ /// @param name is the name of the parameter to delete.
void delParam(const std::string& name) {
values_.erase(name);
positions_.erase(name);
@@ -179,68 +182,6 @@ typedef boost::shared_ptr<StringStorage> StringStoragePtr;
typedef ValueStorage<bool> BooleanStorage;
typedef boost::shared_ptr<BooleanStorage> BooleanStoragePtr;
-/// @brief Container for the current parsing context. It provides a
-/// single enclosure for the storage of configuration parameters,
-/// options, option definitions, and other context specific information
-/// that needs to be accessible throughout the parsing and parsing
-/// constructs.
-class ParserContext {
-public:
- /// @brief Constructor
- ///
- /// @param universe is the Option::Universe value of this
- /// context.
- ParserContext(Option::Universe universe);
-
- /// @brief Copy constructor
- ParserContext(const ParserContext& rhs);
-
- /// @brief Storage for boolean parameters.
- BooleanStoragePtr boolean_values_;
-
- /// @brief Storage for uint32 parameters.
- Uint32StoragePtr uint32_values_;
-
- /// @brief Storage for string parameters.
- StringStoragePtr string_values_;
-
- /// @brief Hooks libraries pointer.
- ///
- /// The hooks libraries information is a vector of strings, each containing
- /// the name of a library. Hooks libraries should only be reloaded if the
- /// list of names has changed, so the list of current DHCP parameters
- /// (in isc::dhcp::CfgMgr) contains an indication as to whether the list has
- /// altered. This indication is implemented by storing a pointer to the
- /// list of library names which is cleared when the libraries are loaded.
- /// So either the pointer is null (meaning don't reload the libraries and
- /// the list of current names can be obtained from the HooksManager) or it
- /// is non-null (this is the new list of names, reload the libraries when
- /// possible).
- isc::hooks::HookLibsCollectionPtr hooks_libraries_;
-
- /// @brief The parsing universe of this context.
- Option::Universe universe_;
-
- /// @brief Assignment operator
- ParserContext& operator=(const ParserContext& rhs);
-
- /// @brief Copy the context fields.
- ///
- /// This class method initializes the context data by copying the data
- /// stored in the context instance provided as an argument. Note that
- /// this function will also handle copying the NULL pointers.
- ///
- /// @param ctx context to be copied.
- void copyContext(const ParserContext& ctx);
-
- template<typename T>
- void copyContextPointer(const boost::shared_ptr<T>& source_ptr,
- boost::shared_ptr<T>& dest_ptr);
-};
-
-/// @brief Pointer to various parser context.
-typedef boost::shared_ptr<ParserContext> ParserContextPtr;
-
/// @brief Simple data-type parser template class
///
/// This is the template class for simple data-type parsers. It supports
@@ -248,7 +189,7 @@ typedef boost::shared_ptr<ParserContext> ParserContextPtr;
/// possible values. It provides a common constructor, commit, and templated
/// data storage. The "build" method implementation must be provided by a
/// declaring type.
-/// @param ValueType is the data type of the configuration paramater value
+/// @param ValueType is the data type of the configuration parameter value
/// the parser should handle.
template<typename ValueType>
class ValueParser : public DhcpConfigParser {
@@ -271,7 +212,7 @@ public:
<< "empty parameter name provided");
}
- // NUll storage is invalid.
+ // Null storage is invalid.
if (!storage_) {
isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
<< "storage may not be NULL");
@@ -364,413 +305,57 @@ private:
};
-/// @brief parser for MAC/hardware aquisition sources
+/// @brief parser for MAC/hardware acquisition sources
///
/// This parser handles Dhcp6/mac-sources entry.
-/// It contains a list of MAC/hardware aquisition source, i.e. methods how
+/// It contains a list of MAC/hardware acquisition source, i.e. methods how
/// MAC address can possibly by obtained in DHCPv6. For a currently supported
/// methods, see @ref isc::dhcp::Pkt::getMAC.
-class MACSourcesListConfigParser : public DhcpConfigParser {
+class MACSourcesListConfigParser : public isc::data::SimpleParser {
public:
-
- /// @brief constructor
- ///
- /// As this is a dedicated parser, it must be used to parse
- /// "mac-sources" parameter only. All other types will throw exception.
- ///
- /// @param param_name name of the configuration parameter being parsed
- /// @param global_context Global parser context.
- /// @throw BadValue if supplied parameter name is not "mac-sources"
- MACSourcesListConfigParser(const std::string& param_name,
- ParserContextPtr global_context);
-
/// @brief parses parameters value
///
/// Parses configuration entry (list of sources) and adds each element
/// to the sources list.
///
/// @param value pointer to the content of parsed values
- virtual void build(isc::data::ConstElementPtr value);
-
- /// @brief Does nothing.
- virtual void commit();
-
-private:
-
- // Parsed parameter name
- std::string param_name_;
-
- /// Global parser context.
- ParserContextPtr global_context_;
+ /// @param mac_sources parsed sources will be stored here
+ void parse(CfgMACSource& mac_sources, isc::data::ConstElementPtr value);
};
/// @brief Parser for the control-socket structure
///
/// It does not parse anything, simply stores the element in
/// the staging config.
-class ControlSocketParser : public DhcpConfigParser {
-public:
-
- ControlSocketParser(const std::string& param_name);
-
- /// @brief Stores contents of the control-socket map in the staging config.
- ///
- /// @param value pointer to the content of parsed values
- virtual void build(isc::data::ConstElementPtr value);
-
- /// @brief Does nothing.
- virtual void commit();
-};
-
-/// @brief Parser for hooks library list
-///
-/// This parser handles the list of hooks libraries. This is an optional list,
-/// which may be empty.
-///
-/// However, the parser does more than just check the list of library names.
-/// It does two other things:
-///
-/// -# The problem faced with the hooks libraries is that we wish to avoid
-/// reloading the libraries if they have not changed. (This would cause the
-/// "unload" and "load" methods to run. Although libraries should be written
-/// to cope with this, it is feasible that such an action may be constly in
-/// terms of time and resources, or may cause side effects such as clearning
-/// an internal cache.) To this end, the parser also checks the list against
-/// the list of libraries current loaded and notes if there are changes.
-/// -# If there are, the parser validates the libraries; it opens them and
-/// checks that the "version" function exists and returns the correct value.
-///
-/// Only if the library list has changed and the libraries are valid will the
-/// change be applied.
-class HooksLibrariesParser : public DhcpConfigParser {
+class ControlSocketParser : public isc::data::SimpleParser {
public:
-
- /// @brief Constructor
- ///
- /// As this is a dedicated parser, it must be used to parse
- /// "hooks-libraries" parameter only. All other types will throw exception.
- ///
- /// @param param_name name of the configuration parameter being parsed.
- ///
- /// @throw BadValue if supplied parameter name is not "hooks-libraries"
- HooksLibrariesParser(const std::string& param_name);
-
- /// @brief Parses parameters value
- ///
- /// Parses configuration entry (list of parameters) and adds each element
- /// to the hooks libraries list. The method also checks whether the
- /// list of libraries is the same as that already loaded. If not, it
- /// checks each of the libraries in the list for validity (they exist and
- /// have a "version" function that returns the correct value).
- ///
- /// The syntax for specifying hooks libraries allow for library-specific
- /// parameters to be specified along with the library, e.g.
- ///
- /// @code
- /// "hooks-libraries": [
- /// {
- /// "library": "hook-lib-1.so",
- /// "parameters": {
- /// "alpha": "a string",
- /// "beta": 42
- /// }
- /// },
- /// :
- /// ]
- /// @endcode
- ///
- /// As Kea has not yet implemented parameters, the parsing code only checks
- /// that:
+ /// @brief "Parses" control-socket structure
///
- /// -# Each element in the hooks-libraries list is a map
- /// -# The map contains an element "library" whose value is a string: all
- /// other elements in the map are ignored.
+ /// Since the SrvConfig structure takes the socket definition
+ /// as ConstElementPtr, there's really nothing to parse here.
+ /// It only does basic sanity checks and throws DhcpConfigError
+ /// if the value is null or is not a map.
///
+ /// @param srv_cfg parsed values will be stored here
/// @param value pointer to the content of parsed values
- virtual void build(isc::data::ConstElementPtr value);
-
- /// @brief Commits hooks libraries data
- ///
- /// Providing that the specified libraries are valid and are different
- /// to those already loaded, this method loads the new set of libraries
- /// (and unloads the existing set).
- virtual void commit();
-
- /// @brief Returns list of parsed libraries
- ///
- /// Principally for testing, this returns the list of libraries as well as
- /// an indication as to whether the list is different from the list of
- /// libraries already loaded.
- ///
- /// @param [out] libraries List of libraries that were specified in the
- /// new configuration.
- /// @param [out] changed true if the list is different from that currently
- /// loaded.
- void getLibraries(isc::hooks::HookLibsCollection& libraries, bool& changed);
-
-private:
- /// List of hooks libraries with their configuration parameters
- isc::hooks::HookLibsCollection libraries_;
-
- /// Indicator flagging that the list of libraries has changed.
- bool changed_;
+ void parse(SrvConfig& srv_cfg, isc::data::ConstElementPtr value);
};
-/// @brief Parser for option data value.
-///
-/// This parser parses configuration entries that specify value of
-/// a single option. These entries include option name, option code
-/// and data carried by the option. The option data can be specified
-/// in one of the two available formats: binary value represented as
-/// a string of hexadecimal digits or a list of comma separated values.
-/// The format being used is controlled by csv-format configuration
-/// parameter. When setting this value to True, the latter format is
-/// used. The subsequent values in the CSV format apply to relevant
-/// option data fields in the configured option. For example the
-/// configuration: "data" : "192.168.2.0, 56, hello world" can be
-/// used to set values for the option comprising IPv4 address,
-/// integer and string data field. Note that order matters. If the
-/// order of values does not match the order of data fields within
-/// an option the configuration will not be accepted. If parsing
-/// is successful then an instance of an option is created and
-/// added to the storage provided by the calling class.
-class OptionDataParser : public DhcpConfigParser {
-public:
- /// @brief Constructor.
- ///
- /// @param dummy first argument is ignored, all Parser constructors
- /// accept string as first argument.
- /// @param [out] cfg Pointer to the configuration object where parsed option
- /// should be stored or NULL if this is a global option.
- /// @param address_family Address family: @c AF_INET or @c AF_INET6.
- /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
- OptionDataParser(const std::string& dummy, const CfgOptionPtr& cfg,
- const uint16_t address_family);
-
- /// @brief Parses the single option data.
- ///
- /// This method parses the data of a single option from the configuration.
- /// The option data includes option name, option code and data being
- /// carried by this option. Eventually it creates the instance of the
- /// option and adds it to the Configuration Manager.
- ///
- /// @param option_data_entries collection of entries that define value
- /// for a particular option.
- /// @throw DhcpConfigError if invalid parameter specified in
- /// the configuration.
- /// @throw isc::InvalidOperation if failed to set storage prior to
- /// calling build.
- virtual void build(isc::data::ConstElementPtr option_data_entries);
-
- /// @brief Does nothing.
- virtual void commit();
-
- /// @brief virtual destructor to ensure orderly destruction of derivations.
- virtual ~OptionDataParser(){};
-private:
-
- /// @brief Finds an option definition within an option space
- ///
- /// Given an option space and an option code, find the correpsonding
- /// option defintion within the option defintion storage.
- ///
- /// @param option_space name of the parameter option space
- /// @param search_key an option code or name to be used to lookup the
- /// option definition.
- /// @tparam A numeric type for searching using an option code or the
- /// string for searching using the option name.
- ///
- /// @return OptionDefintionPtr of the option defintion or an
- /// empty OptionDefinitionPtr if not found.
- /// @throw DhcpConfigError if the option space requested is not valid
- /// for this server.
- template<typename SearchKey>
- OptionDefinitionPtr findOptionDefinition(const std::string& option_space,
- const SearchKey& search_key) const;
-
- /// @brief Create option instance.
- ///
- /// Creates an instance of an option and adds it to the provided
- /// options storage. If the option data parsed by \ref build function
- /// are invalid or insufficient this function emits an exception.
- ///
- /// @warning this function does not check if options_ storage pointer
- /// is intitialized but this check is not needed here because it is done
- /// in the \ref build function.
- ///
- /// @param option_data An element holding data for a single option being
- /// created.
- ///
- /// @throw DhcpConfigError if parameters provided in the configuration
- /// are invalid.
- void createOption(isc::data::ConstElementPtr option_data);
-
- /// @brief Retrieves parsed option code as an optional value.
- ///
- /// @param parent A data element holding full option data configuration.
- /// It is used here to log a position if the element holding a code
- /// is not specified and its position is therefore unavailable.
- ///
- /// @return Option code, possibly unspecified.
- /// @throw DhcpConfigError if option code is invalid.
- util::OptionalValue<uint32_t>
- extractCode(data::ConstElementPtr parent) const;
-
- /// @brief Retrieves parsed option name as an optional value.
- ///
- /// @param parent A data element holding full option data configuration.
- /// It is used here to log a position if the element holding a name
- /// is not specified and its position is therefore unavailable.
- ///
- /// @return Option name, possibly unspecified.
- /// @throw DhcpConfigError if option name is invalid.
- util::OptionalValue<std::string>
- extractName(data::ConstElementPtr parent) const;
-
- /// @brief Retrieves csv-format parameter as an optional value.
- ///
- /// @return Value of the csv-format parameter, possibly unspecified.
- util::OptionalValue<bool> extractCSVFormat() const;
-
- /// @brief Retrieves option data as a string.
- ///
- /// @return Option data as a string. It will return empty string if
- /// option data is unspecified.
- std::string extractData() const;
-
- /// @brief Retrieves option space name.
- ///
- /// If option space name is not specified in the configuration the
- /// 'dhcp4' or 'dhcp6' option space name is returned, depending on
- /// the universe specified in the parser context.
- ///
- /// @return Option space name.
- std::string extractSpace() const;
-
- /// Storage for boolean values.
- BooleanStoragePtr boolean_values_;
-
- /// Storage for string values (e.g. option name or data).
- StringStoragePtr string_values_;
-
- /// Storage for uint32 values (e.g. option code).
- Uint32StoragePtr uint32_values_;
-
- /// Option descriptor holds newly configured option.
- OptionDescriptor option_descriptor_;
-
- /// Option space name where the option belongs to.
- std::string option_space_;
-
- /// @brief Configuration holding option being parsed or NULL if the option
- /// is global.
- CfgOptionPtr cfg_;
-
- /// @brief Address family: @c AF_INET or @c AF_INET6.
- uint16_t address_family_;
-};
-
-///@brief Function pointer for OptionDataParser factory methods
-typedef OptionDataParser *OptionDataParserFactory(const std::string&,
- OptionStoragePtr options, ParserContextPtr global_context);
-
-/// @brief Parser for option data values within a subnet.
-///
-/// This parser iterates over all entries that define options
-/// data for a particular subnet and creates a collection of options.
-/// If parsing is successful, all these options are added to the Subnet
-/// object.
-class OptionDataListParser : public DhcpConfigParser {
-public:
- /// @brief Constructor.
- ///
- /// @param dummy nominally would be param name, this is always ignored.
- /// @param [out] cfg Pointer to the configuration object where options
- /// should be stored or NULL if this is global option scope.
- /// @param address_family Address family: @c AF_INET or AF_INET6
- OptionDataListParser(const std::string& dummy, const CfgOptionPtr& cfg,
- const uint16_t address_family);
-
- /// @brief Parses entries that define options' data for a subnet.
- ///
- /// This method iterates over all entries that define option data
- /// for options within a single subnet and creates options' instances.
- ///
- /// @param option_data_list pointer to a list of options' data sets.
- /// @throw DhcpConfigError if option parsing failed.
- void build(isc::data::ConstElementPtr option_data_list);
-
- /// @brief Commit all option values.
- ///
- /// This function invokes commit for all option values
- /// and append suboptions to the top-level options.
- void commit();
-
-private:
-
- /// Collection of parsers;
- ParserCollection parsers_;
-
- /// @brief Pointer to a configuration where options are stored.
- CfgOptionPtr cfg_;
-
- /// @brief Address family: @c AF_INET or @c AF_INET6
- uint16_t address_family_;
-
-};
-
-typedef boost::shared_ptr<OptionDataListParser> OptionDataListParserPtr;
-
+typedef std::pair<isc::dhcp::OptionDefinitionPtr, std::string> OptionDefinitionTuple;
/// @brief Parser for a single option definition.
///
/// This parser creates an instance of a single option definition.
-class OptionDefParser : public DhcpConfigParser {
+class OptionDefParser : public isc::data::SimpleParser {
public:
- /// @brief Constructor.
- ///
- /// @param dummy first argument is ignored, all Parser constructors
- /// accept string as first argument.
- /// @param global_context is a pointer to the global context which
- /// stores global scope parameters, options, option defintions.
- OptionDefParser(const std::string& dummy, ParserContextPtr global_context);
-
/// @brief Parses an entry that describes single option definition.
///
/// @param option_def a configuration entry to be parsed.
+ /// @return tuple (option definition, option space) of the parsed structure
///
/// @throw DhcpConfigError if parsing was unsuccessful.
- void build(isc::data::ConstElementPtr option_def);
-
- /// @brief Stores the parsed option definition in a storage.
- void commit();
-
-private:
-
- /// @brief Create option definition from the parsed parameters.
- ///
- /// @param option_def_element A data element holding the configuration
- /// for an option definition.
- void createOptionDef(isc::data::ConstElementPtr option_def_element);
-
- /// Instance of option definition being created by this parser.
- OptionDefinitionPtr option_definition_;
-
- /// Name of the space the option definition belongs to.
- std::string option_space_name_;
-
- /// Storage for boolean values.
- BooleanStoragePtr boolean_values_;
-
- /// Storage for string values.
- StringStoragePtr string_values_;
-
- /// Storage for uint32 values.
- Uint32StoragePtr uint32_values_;
-
- /// Parsing context which contains global values, options and option
- /// definitions.
- ParserContextPtr global_context_;
+ OptionDefinitionTuple
+ parse(isc::data::ConstElementPtr option_def);
};
/// @brief Parser for a list of option definitions.
@@ -779,37 +364,17 @@ private:
/// option definitions and creates instances of these definitions.
/// If the parsing is successful, the collection of created definitions
/// is put into the provided storage.
-class OptionDefListParser : public DhcpConfigParser {
+class OptionDefListParser : public isc::data::SimpleParser {
public:
- /// @brief Constructor.
+ /// @brief Parses a list of option definitions, create them and store in cfg
///
- /// @param dummy first argument is ignored, all Parser constructors
- /// accept string as first argument.
- /// @param global_context is a pointer to the global context which
- /// stores global scope parameters, options, option defintions.
- OptionDefListParser(const std::string& dummy,
- ParserContextPtr global_context);
-
- /// @brief Parse configuration entries.
- ///
- /// This function parses configuration entries, creates instances
- /// of option definitions and tries to add them to the Configuration
- /// Manager.
- ///
- /// @param option_def_list pointer to an element that holds entries
- /// that define option definitions.
- /// @throw DhcpConfigError if configuration parsing fails.
- void build(isc::data::ConstElementPtr option_def_list);
-
- /// @brief Commits option definitions.
+ /// This method iterates over def_list, which is a JSON list of option definitions,
+ /// then creates corresponding option definitions and store them in the
+ /// configuration structure.
///
- /// Currently this function is no-op, because option definitions are
- /// added to the Configuration Manager in the @c build method.
- void commit();
-
- /// Parsing context which contains global values, options and option
- /// definitions.
- ParserContextPtr global_context_;
+ /// @param def_list JSON list describing option definitions
+ /// @param cfg parsed option definitions will be stored here
+ void parse(CfgOptionDefPtr cfg, isc::data::ConstElementPtr def_list);
};
/// @brief a collection of pools
@@ -826,32 +391,25 @@ typedef boost::shared_ptr<PoolStorage> PoolStoragePtr;
/// and stored in chosen PoolStorage container.
///
/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[X]/pools[X] structure.
-class PoolParser : public DhcpConfigParser {
+class PoolParser : public isc::data::SimpleParser {
public:
- /// @brief constructor.
- ///
- /// @param dummy first argument is ignored, all Parser constructors
- /// accept string as first argument.
- /// @param pools is the storage in which to store the parsed pool
- /// upon "commit".
- /// @param address_family AF_INET (for DHCPv4) or AF_INET6 (for DHCPv6).
- /// @throw isc::dhcp::DhcpConfigError if storage is null.
- PoolParser(const std::string& dummy, PoolStoragePtr pools,
- const uint16_t address_family);
+ /// @brief destructor.
+ virtual ~PoolParser() {
+ }
/// @brief parses the actual structure
///
/// This method parses the actual list of interfaces.
/// No validation is done at this stage, everything is interpreted as
/// interface name.
+ /// @param pools is the storage in which to store the parsed pool
/// @param pool_structure a single entry on a list of pools
+ /// @param address_family AF_INET (for DHCPv4) or AF_INET6 (for DHCPv6).
/// @throw isc::dhcp::DhcpConfigError when pool parsing fails
- virtual void build(isc::data::ConstElementPtr pool_structure);
-
- /// @brief Stores the parsed values in a storage provided
- /// by an upper level parser.
- virtual void commit();
+ virtual void parse(PoolStoragePtr pools,
+ isc::data::ConstElementPtr pool_structure,
+ const uint16_t address_family);
protected:
/// @brief Creates a Pool object given a IPv4 prefix and the prefix length.
@@ -861,7 +419,7 @@ protected:
/// @param ptype is the type of pool to create.
/// @return returns a PoolPtr to the new Pool object.
virtual PoolPtr poolMaker(isc::asiolink::IOAddress &addr, uint32_t len,
- int32_t ptype=0) = 0;
+ int32_t ptype = 0) = 0;
/// @brief Creates a Pool object given starting and ending IP addresses.
///
@@ -870,23 +428,39 @@ protected:
/// @param ptype is the type of pool to create (not used by all derivations)
/// @return returns a PoolPtr to the new Pool object.
virtual PoolPtr poolMaker(isc::asiolink::IOAddress &min,
- isc::asiolink::IOAddress &max, int32_t ptype=0) = 0;
+ isc::asiolink::IOAddress &max,
+ int32_t ptype = 0) = 0;
+};
- /// @brief pointer to the actual Pools storage
+/// @brief Parser for IPv4 pool definitions.
+///
+/// This is the IPv4 derivation of the PoolParser class and handles pool
+/// definitions, i.e. a list of entries of one of two syntaxes: min-max and
+/// prefix/len for IPv4 pools. Pool4 objects are created and stored in chosen
+/// PoolStorage container.
+///
+/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
+class Pool4Parser : public PoolParser {
+protected:
+ /// @brief Creates a Pool4 object given a IPv4 prefix and the prefix length.
///
- /// That is typically a storage somewhere in Subnet parser
- /// (an upper level parser).
- PoolStoragePtr pools_;
-
- /// A temporary storage for pools configuration. It is a
- /// storage where pools are stored by build function.
- PoolStorage local_pools_;
-
- /// A storage for pool specific option values.
- CfgOptionPtr options_;
-
- /// @brief Address family: AF_INET (for DHCPv4) or AF_INET6 for DHCPv6.
- uint16_t address_family_;
+ /// @param addr is the IPv4 prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ignored dummy parameter to provide symmetry between the
+ /// PoolParser derivations. The V6 derivation requires a third value.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (asiolink::IOAddress &addr, uint32_t len,
+ int32_t ignored);
+
+ /// @brief Creates a Pool4 object given starting and ending IPv4 addresses.
+ ///
+ /// @param min is the first IPv4 address in the pool.
+ /// @param max is the last IPv4 address in the pool.
+ /// @param ignored dummy parameter to provide symmetry between the
+ /// PoolParser derivations. The V6 derivation requires a third value.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (asiolink::IOAddress &min, asiolink::IOAddress &max,
+ int32_t ignored);
};
/// @brief Parser for a list of pools
@@ -894,104 +468,63 @@ protected:
/// This parser parses a list pools. Each element on that list gets its own
/// parser, created with poolParserMaker() method. That method must be specified
/// for each protocol family (v4 or v6) separately.
-///
-/// This class is not intended to be used directly. Instead, derived classes
-/// should implement poolParserMaker() method.
-class PoolsListParser : public DhcpConfigParser {
+class PoolsListParser : public isc::data::SimpleParser {
public:
- /// @brief constructor.
- ///
- /// @param dummy first argument is ignored, all Parser constructors
- /// accept a string as the first argument.
- /// @param pools is the storage in which to store the parsed pool
- /// upon "commit".
- /// @throw isc::dhcp::DhcpConfigError if storage is null.
- PoolsListParser(const std::string& dummy, PoolStoragePtr pools);
+ /// @brief destructor.
+ virtual ~PoolsListParser() {
+ }
/// @brief parses the actual structure
///
- /// This method parses the actual list of pools. It creates a parser
- /// for each structure using poolParserMaker().
+ /// This method parses the actual list of pools.
///
+ /// @param pools is the storage in which to store the parsed pools.
/// @param pools_list a list of pool structures
/// @throw isc::dhcp::DhcpConfigError when pool parsing fails
- virtual void build(isc::data::ConstElementPtr pools_list);
-
- /// @brief Stores the parsed values in storage provided
- /// by an upper level parser.
- virtual void commit();
+ virtual void parse(PoolStoragePtr pools,
+ isc::data::ConstElementPtr pools_list) = 0;
+};
-protected:
+/// @brief Specialization of the pool list parser for DHCPv4
+class Pools4ListParser : PoolsListParser {
+public:
- /// @brief Creates a PoolParser object
+ /// @brief parses the actual structure
///
- /// Instantiates appropriate (v4 or v6) PoolParser object.
- /// @param storage parameter that is passed to ParserMaker() constructor.
- virtual ParserPtr poolParserMaker(PoolStoragePtr storage) = 0;
-
- /// @brief pointer to the actual Pools storage
+ /// This method parses the actual list of pools.
///
- /// That is typically a storage somewhere in Subnet parser
- /// (an upper level parser).
- PoolStoragePtr pools_;
-
- /// A temporary storage for pools configuration. It is the
- /// storage where pools are stored by the build function.
- PoolStoragePtr local_pools_;
-
- /// Collection of parsers;
- ParserCollection parsers_;
+ /// @param pools storage container in which to store the parsed pool.
+ /// @param pools_list a list of pool structures
+ /// @throw isc::dhcp::DhcpConfigError when pool parsing fails
+ void parse(PoolStoragePtr pools, data::ConstElementPtr pools_list);
};
/// @brief parser for additional relay information
///
-/// This concrete parser handles RelayInfo structure defintions.
+/// This concrete parser handles RelayInfo structure definitions.
/// So far that structure holds only relay IP (v4 or v6) address, but it
/// is expected that the number of parameters will increase over time.
///
/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[x]/relay parameters.
-class RelayInfoParser : public DhcpConfigParser {
+class RelayInfoParser : public isc::data::SimpleParser {
public:
/// @brief constructor
- /// @param unused first argument is ignored, all Parser constructors
- /// accept string as first argument.
- /// @param relay_info is the storage in which to store the parsed
/// @param family specifies protocol family (IPv4 or IPv6)
- RelayInfoParser(const std::string& unused,
- const isc::dhcp::Subnet::RelayInfoPtr& relay_info,
- const isc::dhcp::Option::Universe& family);
+ explicit RelayInfoParser(const isc::dhcp::Option::Universe& family);
/// @brief parses the actual relay parameters
- /// @param relay_info JSON structure holding relay parameters to parse
- virtual void build(isc::data::ConstElementPtr relay_info);
-
- /// @brief stores parsed info in relay_info
- virtual void commit();
-
-protected:
-
- /// @brief Creates a parser for the given "relay" member element id.
///
/// The elements currently supported are:
/// -# ip-address
///
- /// @param parser is the "item_name" for a specific member element of
- /// the "relay" specification.
- ///
- /// @return returns a pointer to newly created parser.
- isc::dhcp::ParserPtr
- createConfigParser(const std::string& parser);
-
- /// Parsed data will be stored there on commit()
- isc::dhcp::Subnet::RelayInfoPtr storage_;
-
- /// Local storage information (for temporary values)
- isc::dhcp::Subnet::RelayInfo local_;
+ /// @param cfg configuration will be stored here
+ /// @param relay_info JSON structure holding relay parameters to parse
+ void parse(const isc::dhcp::Subnet::RelayInfoPtr& cfg,
+ isc::data::ConstElementPtr relay_info);
- /// Storage for subnet-specific string values.
- StringStoragePtr string_values_;
+private:
/// Protocol family (IPv4 or IPv6)
Option::Universe family_;
@@ -999,78 +532,56 @@ protected:
/// @brief this class parses a single subnet
///
-/// This class parses the whole subnet definition. It creates parsers
-/// for received configuration parameters as needed.
-class SubnetConfigParser : public DhcpConfigParser {
+/// There are dedicated @ref Subnet4ConfigParser and @ref Subnet6ConfigParser
+/// classes. They provide specialized parse() methods that return Subnet4Ptr
+/// or Subnet6Ptr.
+///
+/// This class parses the whole subnet definition. This class attempts to
+/// unify the code between v4 and v6 as much as possible. As a result, the flow
+/// is somewhat complex and it looks as follows:
+///
+/// ------- Base class
+/// /
+/// | /----- Derived class
+/// 1. * SubnetXConfigParser::parse() is called.
+/// 2. * SubnetConfigParser::parse() is called.
+/// 3. * SubnetConfigParser::createSubnet() is called.
+/// 4. * SubnetXConfigParser::initSubnet() is called (Subnet4 or Subnet6 is
+/// instantiated here and family specific parameters are set)
+/// 5. Control returns to createSubnet() (step 3) and common parameters
+/// are set.
+
+class SubnetConfigParser : public isc::data::SimpleParser {
public:
/// @brief constructor
///
- /// @param global_context
- /// @param default_addr default IP address (0.0.0.0 for IPv4, :: for IPv6)
- SubnetConfigParser(const std::string&, ParserContextPtr global_context,
- const isc::asiolink::IOAddress& default_addr);
+ /// @param family address family: @c AF_INET or @c AF_INET6
+ explicit SubnetConfigParser(uint16_t family);
- /// @brief parses parameter value
- ///
- /// @param subnet pointer to the content of subnet definition
- ///
- /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
- virtual void build(isc::data::ConstElementPtr subnet);
-
- /// @brief Adds the created subnet to a server's configuration.
- virtual void commit() = 0;
+ /// @brief virtual destructor (does nothing)
+ virtual ~SubnetConfigParser() { }
protected:
- /// @brief creates parsers for entries in subnet definition
+ /// @brief parses a subnet description and returns Subnet{4,6} structure
///
- /// @param config_id name od the entry
+ /// This method is called from specialized (Subnet4ConfigParser or
+ /// Subnet6ConfigParser) classes.
///
- /// @return parser object for specified entry name
- /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
- /// for unknown config element
- virtual DhcpConfigParser* createSubnetConfigParser(
- const std::string& config_id) = 0;
-
- /// @brief Issues a server specific warning regarding duplicate subnet
- /// options.
+ /// @param subnet pointer to the content of subnet definition
+ /// @return a pointer to newly created subnet
///
- /// @param code is the numeric option code of the duplicate option
- /// @param addr is the subnet address
- /// @todo a means to know the correct logger and perhaps a common
- /// message would allow this method to be emitted by the base class.
- virtual void duplicate_option_warning(uint32_t code,
- isc::asiolink::IOAddress& addr) = 0;
+ /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
+ SubnetPtr parse(isc::data::ConstElementPtr subnet);
/// @brief Instantiates the subnet based on a given IP prefix and prefix
/// length.
///
+ /// @param params configuration parameters for that subnet
/// @param addr is the IP prefix of the subnet.
/// @param len is the prefix length
- virtual void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) = 0;
-
- /// @brief Returns value for a given parameter (after using inheritance)
- ///
- /// This method implements inheritance. For a given parameter name, it first
- /// checks if there is a global value for it and overwrites it with specific
- /// value if such value was defined in subnet.
- ///
- /// @param name name of the parameter
- /// @return triplet with the parameter name
- /// @throw DhcpConfigError when requested parameter is not present
- isc::dhcp::Triplet<uint32_t> getParam(const std::string& name);
-
- /// @brief Returns optional value for a given parameter.
- ///
- /// This method checks if an optional parameter has been specified for
- /// a subnet. If not, it will try to use a global value. If the global
- /// value is not specified it will return an object representing an
- /// unspecified value.
- ///
- /// @param name name of the configuration parameter.
- /// @return An optional value or a @c Triplet object representing
- /// unspecified value.
- isc::dhcp::Triplet<uint32_t> getOptionalParam(const std::string& name);
+ virtual void initSubnet(isc::data::ConstElementPtr params,
+ isc::asiolink::IOAddress addr, uint8_t len) = 0;
/// @brief Attempts to convert text representation to HRMode enum.
///
@@ -1088,33 +599,21 @@ private:
/// @brief Create a new subnet using a data from child parsers.
///
+ /// @param data Element map that describes the subnet
/// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing
/// failed.
- void createSubnet();
+ void createSubnet(isc::data::ConstElementPtr data);
protected:
- /// Storage for subnet-specific integer values.
- Uint32StoragePtr uint32_values_;
-
- /// Storage for subnet-specific string values.
- StringStoragePtr string_values_;
-
- /// Storage for subnet-specific boolean values.
- BooleanStoragePtr boolean_values_;
-
/// Storage for pools belonging to this subnet.
PoolStoragePtr pools_;
- /// Parsers are stored here.
- ParserCollection parsers_;
-
/// Pointer to the created subnet object.
isc::dhcp::SubnetPtr subnet_;
- /// Parsing context which contains global values, options and option
- /// definitions.
- ParserContextPtr global_context_;
+ /// @brief Address family: @c AF_INET or @c AF_INET6
+ uint16_t address_family_;
/// Pointer to relay information
isc::dhcp::Subnet::RelayInfoPtr relay_info_;
@@ -1123,34 +622,246 @@ protected:
CfgOptionPtr options_;
};
-/// @brief Parser for D2ClientConfig
+/// @anchor Subnet4ConfigParser
+/// @brief This class parses a single IPv4 subnet.
///
-/// This class parses the configuration element "dhcp-ddns" common to the
-/// spec files for both dhcp4 and dhcp6. It creates an instance of a
-/// D2ClientConfig.
-class D2ClientConfigParser : public isc::dhcp::DhcpConfigParser {
+/// This is the IPv4 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
+/// parameters as needed.
+class Subnet4ConfigParser : public SubnetConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// stores global scope parameters, options, option definitions.
+ Subnet4ConfigParser();
+
+ /// @brief Parses a single IPv4 subnet configuration and adds to the
+ /// Configuration Manager.
+ ///
+ /// @param subnet A new subnet being configured.
+ /// @return a pointer to created Subnet4 object
+ Subnet4Ptr parse(data::ConstElementPtr subnet);
+
+protected:
+
+ /// @brief Instantiates the IPv4 Subnet based on a given IPv4 address
+ /// and prefix length.
+ ///
+ /// @param params Data structure describing a subnet.
+ /// @param addr is IPv4 address of the subnet.
+ /// @param len is the prefix length
+ void initSubnet(data::ConstElementPtr params,
+ asiolink::IOAddress addr, uint8_t len);
+};
+
+/// @brief this class parses list of DHCP4 subnets
+///
+/// This is a wrapper parser that handles the whole list of Subnet4
+/// definitions. It iterates over all entries and creates Subnet4ConfigParser
+/// for each entry.
+class Subnets4ListConfigParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief parses contents of the list
+ ///
+ /// Iterates over all entries on the list, parses its content
+ /// (by instantiating Subnet6ConfigParser) and adds to specified
+ /// configuration.
+ ///
+ /// @param cfg Pointer to server configuration.
+ /// @param subnets_list pointer to a list of IPv4 subnets
+ /// @return number of subnets created
+ size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list);
+};
+
+/// @brief Parser for IPv6 pool definitions.
+///
+/// This is the IPv6 derivation of the PoolParser class and handles pool
+/// definitions, i.e. a list of entries of one of two syntaxes: min-max and
+/// prefix/len for IPv6 pools. Pool6 objects are created and stored in chosen
+/// PoolStorage container.
+///
+/// It is useful for parsing Dhcp6/subnet6[X]/pool parameters.
+class Pool6Parser : public PoolParser {
+protected:
+ /// @brief Creates a Pool6 object given a IPv6 prefix and the prefix length.
+ ///
+ /// @param addr is the IPv6 prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ptype is the type of IPv6 pool (Pool::PoolType). Note this is
+ /// passed in as an int32_t and cast to PoolType to accommodate a
+ /// polymorphic interface.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (asiolink::IOAddress &addr, uint32_t len, int32_t ptype);
+
+ /// @brief Creates a Pool6 object given starting and ending IPv6 addresses.
+ ///
+ /// @param min is the first IPv6 address in the pool.
+ /// @param max is the last IPv6 address in the pool.
+ /// @param ptype is the type of IPv6 pool (Pool::PoolType). Note this is
+ /// passed in as an int32_t and cast to PoolType to accommodate a
+ /// polymorphic interface.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (asiolink::IOAddress &min, asiolink::IOAddress &max,
+ int32_t ptype);
+};
+
+/// @brief Specialization of the pool list parser for DHCPv6
+class Pools6ListParser : PoolsListParser {
+public:
+
+ /// @brief parses the actual structure
+ ///
+ /// This method parses the actual list of pools.
+ ///
+ /// @param pools storage container in which to store the parsed pool.
+ /// @param pools_list a list of pool structures
+ /// @throw isc::dhcp::DhcpConfigError when pool parsing fails
+ void parse(PoolStoragePtr pools, data::ConstElementPtr pools_list);
+};
+
+/// @brief Parser for IPv6 prefix delegation definitions.
+///
+/// This class handles prefix delegation pool definitions for IPv6 subnets
+/// Pool6 objects are created and stored in the given PoolStorage container.
+///
+/// PdPool definitions currently support three elements: prefix, prefix-len,
+/// and delegated-len, as shown in the example JSON text below:
+///
+/// @code
+///
+/// {
+/// "prefix": "2001:db8:1::",
+/// "prefix-len": 64,
+/// "delegated-len": 128
+/// }
+/// @endcode
+///
+class PdPoolParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ PdPoolParser();
+
+ /// @brief Builds a prefix delegation pool from the given configuration
+ ///
+ /// This function parses configuration entries and creates an instance
+ /// of a dhcp::Pool6 configured for prefix delegation.
+ ///
+ /// @param pools storage container in which to store the parsed pool.
+ /// @param pd_pool_ pointer to an element that holds configuration entries
+ /// that define a prefix delegation pool.
+ ///
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void parse(PoolStoragePtr pools, data::ConstElementPtr pd_pool_);
+
+private:
+
+ /// Pointer to the created pool object.
+ isc::dhcp::Pool6Ptr pool_;
+
+ /// A storage for pool specific option values.
+ CfgOptionPtr options_;
+
+ isc::data::ConstElementPtr user_context_;
+};
+
+/// @brief Parser for a list of prefix delegation pools.
+///
+/// This parser iterates over a list of prefix delegation pool entries and
+/// creates pool instances for each one. If the parsing is successful, the
+/// collection of pools is committed to the provided storage.
+class PdPoolsListParser : public PoolsListParser {
+public:
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of prefix delegation pools .
+ ///
+ /// @param storage is the pool storage in which to store the parsed
+ /// @param pd_pool_list pointer to an element that holds entries
+ /// that define a prefix delegation pool.
+ ///
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void parse(PoolStoragePtr pools, data::ConstElementPtr pd_pool_list);
+};
+
+/// @anchor Subnet6ConfigParser
+/// @brief This class parses a single IPv6 subnet.
+///
+/// This is the IPv6 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
+/// parameters as needed.
+class Subnet6ConfigParser : public SubnetConfigParser {
public:
+
/// @brief Constructor
///
- /// @param entry_name is an arbitrary label assigned to this configuration
- /// definition.
- D2ClientConfigParser(const std::string& entry_name);
+ /// stores global scope parameters, options, option definitions.
+ Subnet6ConfigParser();
- /// @brief Destructor
- virtual ~D2ClientConfigParser();
+ /// @brief Parses a single IPv6 subnet configuration and adds to the
+ /// Configuration Manager.
+ ///
+ /// @param subnet A new subnet being configured.
+ /// @return a pointer to created Subnet6 object
+ Subnet6Ptr parse(data::ConstElementPtr subnet);
- /// @brief Performs the parsing of the given dhcp-ddns element.
+protected:
+ /// @brief Issues a DHCP6 server specific warning regarding duplicate subnet
+ /// options.
///
- /// The results of the parsing are retained internally for use during
- /// commit.
- /// @todo This parser supplies hard-coded default values for all
- /// optional parameters. This should be changed once a new plan
- /// for configuration is determined.
+ /// @param code is the numeric option code of the duplicate option
+ /// @param addr is the subnet address
+ /// @todo A means to know the correct logger and perhaps a common
+ /// message would allow this message to be emitted by the base class.
+ virtual void duplicate_option_warning(uint32_t code,
+ asiolink::IOAddress& addr);
+
+ /// @brief Instantiates the IPv6 Subnet based on a given IPv6 address
+ /// and prefix length.
///
- /// @param client_config is the "dhcp-ddns" configuration to parse
- virtual void build(isc::data::ConstElementPtr client_config);
+ /// @param params Data structure describing a subnet.
+ /// @param addr is IPv6 prefix of the subnet.
+ /// @param len is the prefix length
+ void initSubnet(isc::data::ConstElementPtr params,
+ isc::asiolink::IOAddress addr, uint8_t len);
+};
+
- /// @brief Creates a parser for the given "dhcp-ddns" member element id.
+/// @brief this class parses a list of DHCP6 subnets
+///
+/// This is a wrapper parser that handles the whole list of Subnet6
+/// definitions. It iterates over all entries and creates Subnet6ConfigParser
+/// for each entry.
+class Subnets6ListConfigParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief parses contents of the list
+ ///
+ /// Iterates over all entries on the list, parses its content
+ /// (by instantiating Subnet6ConfigParser) and adds to specified
+ /// configuration.
+ ///
+ /// @param cfg configuration (parsed subnets will be stored here)
+ /// @param subnets_list pointer to a list of IPv6 subnets
+ /// @throw DhcpConfigError if CfgMgr rejects the subnet (e.g. subnet-id is a duplicate)
+ size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list);
+};
+
+/// @brief Parser for D2ClientConfig
+///
+/// This class parses the configuration element "dhcp-ddns" common to the
+/// config files for both dhcp4 and dhcp6. It creates an instance of a
+/// D2ClientConfig.
+class D2ClientConfigParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Parses a given dhcp-ddns element into D2ClientConfig.
+ ///
+ /// @param d2_client_cfg is the "dhcp-ddns" configuration to parse
///
/// The elements currently supported are (see isc::dhcp::D2ClientConfig
/// for details on each):
@@ -1158,50 +869,66 @@ public:
/// -# qualifying-suffix
/// -# server-ip
/// -# server-port
+ /// -# sender-ip
+ /// -# sender-port
+ /// -# max-queue-size
/// -# ncr-protocol
/// -# ncr-format
- /// -# remove-on-renew
/// -# always-include-fqdn
- /// -# allow-client-update
/// -# override-no-update
/// -# override-client-update
/// -# replace-client-name
/// -# generated-prefix
///
- /// @param config_id is the "item_name" for a specific member element of
- /// the "dns_server" specification.
- ///
- /// @return returns a pointer to newly created parser.
- virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
- config_id);
+ /// @return returns a pointer to newly created D2ClientConfig.
+ D2ClientConfigPtr parse(isc::data::ConstElementPtr d2_client_cfg);
- /// @brief Instantiates a D2ClientConfig from internal data values
- /// passes to CfgMgr singleton.
- virtual void commit();
+ /// @brief Defaults for the D2 client configuration.
+ static const isc::data::SimpleDefaults D2_CLIENT_CONFIG_DEFAULTS;
-private:
- /// @brief Arbitrary label assigned to this parser instance.
- /// Primarily used for diagnostics.
- std::string entry_name_;
+ /// @brief Sets all defaults for D2 client configuration.
+ ///
+ /// This method sets defaults value. It must not be called
+ /// before the short cut disabled updates condition was checked.
+ ///
+ /// @param d2_config d2 client configuration (will be const cast
+ // to ElementPtr)
+ /// @return number of parameters inserted
+ static size_t setAllDefaults(isc::data::ConstElementPtr d2_config);
- /// Storage for subnet-specific boolean values.
- BooleanStoragePtr boolean_values_;
+private:
- /// Storage for subnet-specific integer values.
- Uint32StoragePtr uint32_values_;
+ /// @brief Returns a value converted to NameChangeProtocol
+ ///
+ /// Instantiation of getAndConvert() to NameChangeProtocol
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a NameChangeProtocol value
+ dhcp_ddns::NameChangeProtocol
+ getProtocol(isc::data::ConstElementPtr scope, const std::string& name);
- /// Storage for subnet-specific string values.
- StringStoragePtr string_values_;
+ /// @brief Returns a value converted to NameChangeFormat
+ ///
+ /// Instantiation of getAndConvert() to NameChangeFormat
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a NameChangeFormat value
+ dhcp_ddns::NameChangeFormat
+ getFormat(isc::data::ConstElementPtr scope, const std::string& name);
- /// @brief Pointer to temporary local instance created during build.
- D2ClientConfigPtr local_client_config_ ;
+ /// @brief Returns a value converted to ReplaceClientNameMode
+ ///
+ /// Instantiation of getAndConvert() to ReplaceClientNameMode
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a NameChangeFormat value
+ D2ClientConfig::ReplaceClientNameMode
+ getMode(isc::data::ConstElementPtr scope, const std::string& name);
};
-// Pointers to various parser objects.
-typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
-typedef boost::shared_ptr<StringParser> StringParserPtr;
-typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
-
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/lib/dhcpsrv/parsers/duid_config_parser.cc b/src/lib/dhcpsrv/parsers/duid_config_parser.cc
index f64c783d10..355e4f7e71 100644
--- a/src/lib/dhcpsrv/parsers/duid_config_parser.cc
+++ b/src/lib/dhcpsrv/parsers/duid_config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,6 +11,7 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/parsers/duid_config_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <exceptions/exceptions.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
@@ -22,115 +23,66 @@ using namespace isc::data;
namespace isc {
namespace dhcp {
-DUIDConfigParser::DUIDConfigParser()
- : DhcpConfigParser() {
-}
-
void
-DUIDConfigParser::build(isc::data::ConstElementPtr duid_configuration) {
- bool type_present = false;
- BOOST_FOREACH(ConfigPair element, duid_configuration->mapValue()) {
- try {
- if (element.first == "type") {
- type_present = true;
- setType(element.second->stringValue());
- } else if (element.first == "identifier") {
- setIdentifier(element.second->stringValue());
- } else if (element.first == "htype") {
- setHType(element.second->intValue());
- } else if (element.first == "time") {
- setTime(element.second->intValue());
- } else if (element.first == "enterprise-id") {
- setEnterpriseId(element.second->intValue());
- } else if (element.first == "persist") {
- setPersist(element.second->boolValue());
- } else {
- isc_throw(DhcpConfigError, "unsupported configuration "
- "parameter '" << element.first << "'");
- }
- } catch (const std::exception& ex) {
- // Append position.
- isc_throw(DhcpConfigError, ex.what() << " ("
- << element.second->getPosition() << ")");
- }
- }
-
- // "type" is mandatory
- if (!type_present) {
- isc_throw(DhcpConfigError, "mandatory parameter \"type\" not specified"
- " for the DUID configuration ("
- << duid_configuration->getPosition() << ")");
+DUIDConfigParser::parse(const CfgDUIDPtr& cfg,
+ isc::data::ConstElementPtr duid_configuration) {
+ if (!cfg) {
+ isc_throw(DhcpConfigError, "Must provide valid pointer to cfg when parsing duid");
}
- LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIGURE_SERVERID);
-}
-
-void
-DUIDConfigParser::setType(const std::string& duid_type) const {
- // Map DUID type represented as text into numeric value.
- DUID::DUIDType numeric_type = DUID::DUID_UNKNOWN;
- if (duid_type == "LLT") {
- numeric_type = DUID::DUID_LLT;
- } else if (duid_type == "EN") {
- numeric_type = DUID::DUID_EN;
- } else if (duid_type == "LL") {
- numeric_type = DUID::DUID_LL;
- } else {
- isc_throw(DhcpConfigError, "unsupported DUID type '"
- << duid_type << "'. Expected: LLT, EN or LL");
- }
-
- const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
- cfg->setType(static_cast<DUID::DUIDType>(numeric_type));
-}
-
-void
-DUIDConfigParser::setIdentifier(const std::string& identifier) const {
- const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
- cfg->setIdentifier(identifier);
-}
+ std::string param;
+ try {
+ param = "type";
+ std::string duid_type = getString(duid_configuration, "type");
+ // Map DUID type represented as text into numeric value.
+ DUID::DUIDType numeric_type = DUID::DUID_UNKNOWN;
+ if (duid_type == "LLT") {
+ numeric_type = DUID::DUID_LLT;
+ } else if (duid_type == "EN") {
+ numeric_type = DUID::DUID_EN;
+ } else if (duid_type == "LL") {
+ numeric_type = DUID::DUID_LL;
+ } else {
+ isc_throw(BadValue, "unsupported DUID type '"
+ << duid_type << "'. Expected: LLT, EN or LL");
+ }
-void
-DUIDConfigParser::setHType(const int64_t htype) const {
- const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
- checkRange<uint16_t>("htype", htype);
- cfg->setHType(static_cast<uint16_t>(htype));
+ cfg->setType(static_cast<DUID::DUIDType>(numeric_type));
-}
+ param = "identifier";
+ if (duid_configuration->contains(param)) {
+ cfg->setIdentifier(getString(duid_configuration, param));
+ }
-void
-DUIDConfigParser::setTime(const int64_t new_time) const {
- const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
- checkRange<uint32_t>("time", new_time);
- cfg->setTime(static_cast<uint32_t>(new_time));
-}
+ param = "htype";
+ if (duid_configuration->contains(param)) {
+ cfg->setHType(getUint16(duid_configuration, param));
+ }
-void
-DUIDConfigParser::setEnterpriseId(const int64_t enterprise_id) const {
- const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
- checkRange<uint32_t>("enterprise-id", enterprise_id);
- cfg->setEnterpriseId(static_cast<uint32_t>(enterprise_id));
-}
+ param = "time";
+ if (duid_configuration->contains(param)) {
+ cfg->setTime(getUint32(duid_configuration, param));
+ }
-void
-DUIDConfigParser::setPersist(const bool persist) {
- const CfgDUIDPtr& cfg = CfgMgr::instance().getStagingCfg()->getCfgDUID();
- cfg->setPersist(persist);
-}
+ param = "enterprise-id";
+ if (duid_configuration->contains(param)) {
+ cfg->setEnterpriseId(getUint32(duid_configuration, param));
+ }
-template<typename NumericType>
-void
-DUIDConfigParser::checkRange(const std::string& parameter_name,
- const int64_t parameter_value) const {
- if ((parameter_value < 0) ||
- (parameter_value > std::numeric_limits<NumericType>::max())) {
- isc_throw(DhcpConfigError, "out of range value '" << parameter_value
- << "' specified for parameter '" << parameter_name
- << "'; expected value in range of [0.."
- << std::numeric_limits<NumericType>::max() << "]");
+ param = "persist";
+ if (duid_configuration->contains(param)) {
+ cfg->setPersist(getBoolean(duid_configuration, param));
+ }
+ } catch (const DhcpConfigError&) {
+ throw;
+ } catch (const std::exception& ex) {
+ // Append position.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << getPosition(param, duid_configuration) << ")");
}
-}
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIGURE_SERVERID);
+}
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/duid_config_parser.h b/src/lib/dhcpsrv/parsers/duid_config_parser.h
index e444a44fcb..8780031147 100644
--- a/src/lib/dhcpsrv/parsers/duid_config_parser.h
+++ b/src/lib/dhcpsrv/parsers/duid_config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,7 +8,7 @@
#define DUID_CONFIG_PARSER_H
#include <cc/data.h>
-#include <dhcpsrv/parsers/dhcp_config_parser.h>
+#include <cc/simple_parser.h>
#include <stdint.h>
#include <string>
@@ -23,68 +23,16 @@ namespace dhcp {
/// - DUID-LL
///
/// @todo Add support for DUID-UUID in the parser.
-class DUIDConfigParser : public DhcpConfigParser {
+class DUIDConfigParser : public isc::data::SimpleParser {
public:
-
- /// @brief Constructor.
- DUIDConfigParser();
-
/// @brief Parses DUID configuration.
///
+ /// @param cfg parsed DUID configuration will be stored here
/// @param duid_configuration Data element holding a map representing
/// DUID configuration.
///
/// @throw DhcpConfigError If the configuration is invalid.
- virtual void build(isc::data::ConstElementPtr duid_configuration);
-
- /// @brief Commit, unused.
- virtual void commit() { }
-
-private:
-
- /// @brief Validate and set DUID type.
- ///
- /// @param duid_type DUID type in textfual format.
- void setType(const std::string& duid_type) const;
-
- /// @brief Validate and set identifier.
- ///
- /// @param identifier Identifier.
- void setIdentifier(const std::string& identifier) const;
-
- /// @brief Validate and set hardware type.
- ///
- /// @param htype Hardware type.
- void setHType(const int64_t htype) const;
-
- /// @brief Validate and set time value.
- ///
- /// @param new_time Time value to be used for DUID.
- void setTime(const int64_t new_time) const;
-
- /// @brief Validate and set enterprise id.
- ///
- /// @param enterprise_id Enterprise id.
- void setEnterpriseId(const int64_t enterprise_id) const;
-
- /// @brief Set persistence flag.
- ///
- /// @param persist A boolean value indicating if the server
- /// identifier should be stored on the disk (if true) or
- /// not (if false).
- void setPersist(const bool persist);
-
- /// @brief Verifies if the specified parameter is in range.
- ///
- /// Each numeric value must be in range of [0 .. max_value], where
- /// max_value is a maximum value for the numeric type used for this
- /// parameter.
- ///
- /// @param parameter_name Parameter name.
- /// @tparam Numeric type of the specified parameter.
- template<typename NumericType>
- void checkRange(const std::string& parameter_name,
- const int64_t parameter_value) const;
+ void parse(const CfgDUIDPtr& cfg, isc::data::ConstElementPtr duid_configuration);
};
}
diff --git a/src/lib/dhcpsrv/parsers/expiration_config_parser.cc b/src/lib/dhcpsrv/parsers/expiration_config_parser.cc
index 4bd8bfe4e6..94e30e9580 100644
--- a/src/lib/dhcpsrv/parsers/expiration_config_parser.cc
+++ b/src/lib/dhcpsrv/parsers/expiration_config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#include <dhcpsrv/cfg_expiration.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/parsers/expiration_config_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <boost/foreach.hpp>
using namespace isc::data;
@@ -15,58 +16,52 @@ using namespace isc::data;
namespace isc {
namespace dhcp {
-ExpirationConfigParser::ExpirationConfigParser()
- : DhcpConfigParser() {
-}
-
void
-ExpirationConfigParser::build(ConstElementPtr expiration_config) {
+ExpirationConfigParser::parse(ConstElementPtr expiration_config) {
CfgExpirationPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgExpiration();
- BOOST_FOREACH(ConfigPair config_element, expiration_config->mapValue()) {
-
- // Get parameter name and value.
- std::string param_name = config_element.first;
- ConstElementPtr param_value = config_element.second;
+ std::string param;
- try {
- // Set configuration parameters.
- if (param_name == "reclaim-timer-wait-time") {
- cfg->setReclaimTimerWaitTime(param_value->intValue());
-
- } else if (param_name == "flush-reclaimed-timer-wait-time") {
- cfg->setFlushReclaimedTimerWaitTime(param_value->intValue());
-
- } else if (param_name == "hold-reclaimed-time") {
- cfg->setHoldReclaimedTime(param_value->intValue());
+ try {
+ param = "reclaim-timer-wait-time";
+ if (expiration_config->contains(param)) {
+ cfg->setReclaimTimerWaitTime(getInteger(expiration_config, param));
+ }
- } else if (param_name == "max-reclaim-leases") {
- cfg->setMaxReclaimLeases(param_value->intValue());
+ param = "flush-reclaimed-timer-wait-time";
+ if (expiration_config->contains(param)) {
+ cfg->setFlushReclaimedTimerWaitTime(getInteger(expiration_config,
+ param));
+ }
- } else if (param_name == "max-reclaim-time") {
- cfg->setMaxReclaimTime(param_value->intValue());
+ param = "hold-reclaimed-time";
+ if (expiration_config->contains(param)) {
+ cfg->setHoldReclaimedTime(getInteger(expiration_config, param));
+ }
- } else if (param_name == "unwarned-reclaim-cycles") {
- cfg->setUnwarnedReclaimCycles(param_value->intValue());
+ param = "max-reclaim-leases";
+ if (expiration_config->contains(param)) {
+ cfg->setMaxReclaimLeases(getInteger(expiration_config, param));
+ }
- } else {
- isc_throw(DhcpConfigError, "unsupported parameter '"
- << param_name << "'");
- }
+ param = "max-reclaim-time";
+ if (expiration_config->contains(param)) {
+ cfg->setMaxReclaimTime(getInteger(expiration_config, param));
+ }
- } catch (const std::exception& ex) {
- // Append position of the configuration parameter to the error
- // message.
- isc_throw(DhcpConfigError, ex.what() << " ("
- << param_value->getPosition() << ")");
+ param = "unwarned-reclaim-cycles";
+ if (expiration_config->contains(param)) {
+ cfg->setUnwarnedReclaimCycles(
+ getInteger(expiration_config, param));
}
+ } catch (const DhcpConfigError&) {
+ throw;
+ } catch (const std::exception& ex) {
+ // Append position of the configuration parameter to the error message.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << getPosition(param, expiration_config) << ")");
}
}
-void
-ExpirationConfigParser::commit() {
- // Nothing to do.
-}
-
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/expiration_config_parser.h b/src/lib/dhcpsrv/parsers/expiration_config_parser.h
index cf3f2ac3f4..44ba77d6e0 100644
--- a/src/lib/dhcpsrv/parsers/expiration_config_parser.h
+++ b/src/lib/dhcpsrv/parsers/expiration_config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,7 +7,8 @@
#ifndef EXPIRATION_CONFIG_PARSER_H
#define EXPIRATION_CONFIG_PARSER_H
-#include <dhcpsrv/parsers/dhcp_config_parser.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
namespace isc {
namespace dhcp {
@@ -32,26 +33,23 @@ namespace dhcp {
/// those that aren't specified.
///
/// The parser checks if the values of the specified parameters are within
-/// the allowed ranges and throws exception if they are. Each parameter
+/// the allowed ranges and throws exception if they aren't. Each parameter
/// has a corresponding maximum value defined in the @c CfgExpiration class.
/// None of them may be negative.
-class ExpirationConfigParser : public DhcpConfigParser {
+class ExpirationConfigParser : public isc::data::SimpleParser {
public:
- /// @brief Constructor
- ExpirationConfigParser();
+ /// @brief Destructor.
+ virtual ~ExpirationConfigParser() { }
/// @brief Parses parameters in the JSON map, pertaining to the processing
/// of the expired leases.
///
- /// @param value pointer to the content of parsed values
+ /// @param expiration_config pointer to the content of parsed values
///
/// @throw DhcpConfigError if unknown parameter specified or the
/// parameter contains invalid value..
- virtual void build(isc::data::ConstElementPtr value);
-
- /// @brief Does nothing.
- virtual void commit();
+ void parse(isc::data::ConstElementPtr expiration_config);
};
diff --git a/src/lib/dhcpsrv/parsers/host_reservation_parser.cc b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc
index 7a4055ee1c..d066c1f250 100644
--- a/src/lib/dhcpsrv/parsers/host_reservation_parser.cc
+++ b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,8 +7,8 @@
#include <config.h>
#include <asiolink/io_address.h>
#include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <algorithm>
@@ -42,6 +42,7 @@ getSupportedParams4(const bool identifiers_only = false) {
identifiers_set.insert("duid");
identifiers_set.insert("circuit-id");
identifiers_set.insert("client-id");
+ identifiers_set.insert("flex-id");
}
// Copy identifiers and add all other parameters.
if (params_set.empty()) {
@@ -76,6 +77,7 @@ getSupportedParams6(const bool identifiers_only = false) {
if (identifiers_set.empty()) {
identifiers_set.insert("hw-address");
identifiers_set.insert("duid");
+ identifiers_set.insert("flex-id");
}
// Copy identifiers and add all other parameters.
if (params_set.empty()) {
@@ -94,20 +96,24 @@ getSupportedParams6(const bool identifiers_only = false) {
namespace isc {
namespace dhcp {
-HostReservationParser::HostReservationParser(const SubnetID& subnet_id)
- : DhcpConfigParser(), subnet_id_(subnet_id) {
+HostPtr
+HostReservationParser::parse(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data) {
+ return (parseInternal(subnet_id, reservation_data));
}
-void
-HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
+HostPtr
+HostReservationParser::parseInternal(const SubnetID&,
+ isc::data::ConstElementPtr reservation_data) {
std::string identifier;
std::string identifier_name;
std::string hostname;
+ HostPtr host;
try {
// Gather those parameters that are common for both IPv4 and IPv6
// reservations.
- BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
+ BOOST_FOREACH(auto element, reservation_data->mapValue()) {
// Check if we support this parameter.
if (!isSupportedParameter(element.first)) {
isc_throw(DhcpConfigError, "unsupported configuration"
@@ -148,7 +154,7 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
}
// Create a host object from the basic parameters we already parsed.
- host_.reset(new Host(identifier, identifier_name, SubnetID(0),
+ host.reset(new Host(identifier, identifier_name, SubnetID(0),
SubnetID(0), IOAddress("0.0.0.0"), hostname));
} catch (const std::exception& ex) {
@@ -156,18 +162,8 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
isc_throw(DhcpConfigError, ex.what() << " ("
<< reservation_data->getPosition() << ")");
}
-}
-
-void
-HostReservationParser::addHost(isc::data::ConstElementPtr reservation_data) {
- try {
- CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host_);
- } catch (const std::exception& ex) {
- // Append line number to the exception string.
- isc_throw(DhcpConfigError, ex.what() << " ("
- << reservation_data->getPosition() << ")");
- }
+ return (host);
}
bool
@@ -180,45 +176,46 @@ HostReservationParser::isSupportedParameter(const std::string& param_name) const
return (getSupportedParameters(false).count(param_name) > 0);
}
-HostReservationParser4::HostReservationParser4(const SubnetID& subnet_id)
- : HostReservationParser(subnet_id) {
-}
-
-void
-HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) {
- HostReservationParser::build(reservation_data);
+HostPtr
+HostReservationParser4::parseInternal(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data) {
+ HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data);
- host_->setIPv4SubnetID(subnet_id_);
+ host->setIPv4SubnetID(subnet_id);
- BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
+ BOOST_FOREACH(auto element, reservation_data->mapValue()) {
// For 'option-data' element we will use another parser which
// already returns errors with position appended, so don't
// surround it with try-catch.
if (element.first == "option-data") {
- CfgOptionPtr cfg_option = host_->getCfgOption4();
- OptionDataListParser parser(element.first, cfg_option, AF_INET);
- parser.build(element.second);
+ CfgOptionPtr cfg_option = host->getCfgOption4();
+
+ // This parser is converted to SimpleParser already. It
+ // parses the Element structure immediately, there's no need
+ // to go through build/commit phases.
+ OptionDataListParser parser(AF_INET);
+ parser.parse(cfg_option, element.second);
// Everything else should be surrounded with try-catch to append
// position.
} else {
try {
if (element.first == "ip-address") {
- host_->setIPv4Reservation(IOAddress(element.second->
+ host->setIPv4Reservation(IOAddress(element.second->
stringValue()));
} else if (element.first == "next-server") {
- host_->setNextServer(IOAddress(element.second->stringValue()));
+ host->setNextServer(IOAddress(element.second->stringValue()));
} else if (element.first == "server-hostname") {
- host_->setServerHostname(element.second->stringValue());
+ host->setServerHostname(element.second->stringValue());
} else if (element.first == "boot-file-name") {
- host_->setBootFileName(element.second->stringValue());
+ host->setBootFileName(element.second->stringValue());
} else if (element.first == "client-classes") {
BOOST_FOREACH(ConstElementPtr class_element,
element.second->listValue()) {
- host_->addClientClass4(class_element->stringValue());
+ host->addClientClass4(class_element->stringValue());
}
}
@@ -230,7 +227,7 @@ HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) {
}
}
- addHost(reservation_data);
+ return (host);
}
const std::set<std::string>&
@@ -238,25 +235,26 @@ HostReservationParser4::getSupportedParameters(const bool identifiers_only) cons
return (getSupportedParams4(identifiers_only));
}
-HostReservationParser6::HostReservationParser6(const SubnetID& subnet_id)
- : HostReservationParser(subnet_id) {
-}
-
-void
-HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
- HostReservationParser::build(reservation_data);
+HostPtr
+HostReservationParser6::parseInternal(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data) {
+ HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data);
- host_->setIPv6SubnetID(subnet_id_);
+ host->setIPv6SubnetID(subnet_id);
- BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
+ BOOST_FOREACH(auto element, reservation_data->mapValue()) {
// Parse option values. Note that the configuration option parser
// returns errors with position information appended, so there is no
// need to surround it with try-clause (and rethrow with position
// appended).
if (element.first == "option-data") {
- CfgOptionPtr cfg_option = host_->getCfgOption6();
- OptionDataListParser parser(element.first, cfg_option, AF_INET6);
- parser.build(element.second);
+ CfgOptionPtr cfg_option = host->getCfgOption6();
+
+ // This parser is converted to SimpleParser already. It
+ // parses the Element structure immediately, there's no need
+ // to go through build/commit phases.
+ OptionDataListParser parser(AF_INET6);
+ parser.parse(cfg_option, element.second);
} else if (element.first == "ip-addresses" || element.first == "prefixes") {
BOOST_FOREACH(ConstElementPtr prefix_element,
@@ -290,7 +288,7 @@ HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
// Convert the prefix length from the string to the
// number. Note, that we don't use the uint8_t type
- // as the lexical cast would expect a chracter, e.g.
+ // as the lexical cast would expect a character, e.g.
// 'a', instead of prefix length, e.g. '64'.
try {
prefix_len = boost::lexical_cast<
@@ -311,7 +309,7 @@ HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
}
// Create a reservation for an address or prefix.
- host_->addReservation(IPv6Resrv(resrv_type,
+ host->addReservation(IPv6Resrv(resrv_type,
IOAddress(prefix),
prefix_len));
@@ -327,7 +325,7 @@ HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
try {
BOOST_FOREACH(ConstElementPtr class_element,
element.second->listValue()) {
- host_->addClientClass6(class_element->stringValue());
+ host->addClientClass6(class_element->stringValue());
}
} catch (const std::exception& ex) {
// Append line number where the error occurred.
@@ -337,8 +335,7 @@ HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
}
}
- // This may fail, but the addHost function will handle this on its own.
- addHost(reservation_data);
+ return (host);
}
const std::set<std::string>&
@@ -351,7 +348,12 @@ HostReservationIdsParser::HostReservationIdsParser()
}
void
-HostReservationIdsParser::build(isc::data::ConstElementPtr ids_list) {
+HostReservationIdsParser::parse(isc::data::ConstElementPtr ids_list) {
+ parseInternal(ids_list);
+}
+
+void
+HostReservationIdsParser::parseInternal(isc::data::ConstElementPtr ids_list) {
// Remove existing identifier types.
staging_cfg_->clearIdentifierTypes();
diff --git a/src/lib/dhcpsrv/parsers/host_reservation_parser.h b/src/lib/dhcpsrv/parsers/host_reservation_parser.h
index 42ded9c68f..b6542c4af7 100644
--- a/src/lib/dhcpsrv/parsers/host_reservation_parser.h
+++ b/src/lib/dhcpsrv/parsers/host_reservation_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,45 +8,48 @@
#define HOST_RESERVATION_PARSER_H
#include <cc/data.h>
+#include <cc/simple_parser.h>
#include <dhcpsrv/host.h>
-#include <dhcpsrv/parsers/dhcp_config_parser.h>
namespace isc {
namespace dhcp {
/// @brief Parser for a single host reservation entry.
-class HostReservationParser : public DhcpConfigParser {
+class HostReservationParser : public isc::data::SimpleParser {
public:
- /// @brief Constructor.
- ///
- /// @param subnet_id Identifier of the subnet that the host is
- /// connected to.
- HostReservationParser(const SubnetID& subnet_id);
+ /// @brief Destructor.
+ virtual ~HostReservationParser() { }
/// @brief Parses a single entry for host reservation.
///
+ /// @param subnet_id Identifier of the subnet that the host is
+ /// connected to.
/// @param reservation_data Data element holding map with a host
/// reservation configuration.
///
+ /// @return Pointer to the object representing parsed host.
/// @throw DhcpConfigError If the configuration is invalid.
- virtual void build(isc::data::ConstElementPtr reservation_data);
-
- /// @brief Commit, unused.
- virtual void commit() { }
+ virtual HostPtr
+ parse(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data) final;
protected:
- /// @brief Inserts @c host_ object to the staging configuration.
+ /// @brief Parses a single entry for host reservation.
///
- /// This method should be called by derived classes to insert the fully
- /// parsed host reservation configuration to the @c CfgMgr.
+ /// This method is called by @ref parse and it can be overridden in the
+ /// derived classes to provide class specific parsing logic.
///
- /// @param reservation_data Data element holding host reservation. It
- /// used by this method to append the line number to the error string.
+ /// @param subnet_id Identifier of the subnet that the host is
+ /// connected to.
+ /// @param reservation_data Data element holding map with a host
+ /// reservation configuration.
///
- /// @throw DhcpConfigError When operation to add a configured host fails.
- void addHost(isc::data::ConstElementPtr reservation_data);
+ /// @return Pointer to the object representing parsed host.
+ /// @throw DhcpConfigError If the configuration is invalid.
+ virtual HostPtr parseInternal(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data);
/// @brief Checks if the specified parameter is a host identifier.
///
@@ -72,35 +75,23 @@ protected:
/// @return Set of supported parameter names.
virtual const std::set<std::string>&
getSupportedParameters(const bool identifiers_only) const = 0;
-
- /// @brief Identifier of the subnet that the host is connected to.
- SubnetID subnet_id_;
-
- /// @brief Holds a pointer to @c Host object representing a parsed
- /// host reservation configuration.
- HostPtr host_;
-
};
/// @brief Parser for a single host reservation for DHCPv4.
class HostReservationParser4 : public HostReservationParser {
-public:
+protected:
- /// @brief Constructor.
+ /// @brief Parses a single host reservation for DHCPv4.
///
/// @param subnet_id Identifier of the subnet that the host is
/// connected to.
- HostReservationParser4(const SubnetID& subnet_id);
-
- /// @brief Parses a single host reservation for DHCPv4.
- ///
/// @param reservation_data Data element holding map with a host
/// reservation configuration.
///
+ /// @return Pointer to the object representing parsed host.
/// @throw DhcpConfigError If the configuration is invalid.
- virtual void build(isc::data::ConstElementPtr reservation_data);
-
-protected:
+ virtual HostPtr parseInternal(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data);
/// @brief Returns set of the supported parameters for DHCPv4.
///
@@ -111,28 +102,23 @@ protected:
/// @return Set of supported parameter names.
virtual const std::set<std::string>&
getSupportedParameters(const bool identifiers_only) const;
-
};
/// @brief Parser for a single host reservation for DHCPv6.
class HostReservationParser6 : public HostReservationParser {
-public:
+protected:
- /// @brief Constructor.
+ /// @brief Parses a single host reservation for DHCPv6.
///
/// @param subnet_id Identifier of the subnet that the host is
/// connected to.
- HostReservationParser6(const SubnetID& subnet_id);
-
- /// @brief Parses a single host reservation for DHCPv6.
- ///
/// @param reservation_data Data element holding map with a host
/// reservation configuration.
///
+ /// @return Pointer to the object representing parsed host.
/// @throw DhcpConfigError If the configuration is invalid.
- virtual void build(isc::data::ConstElementPtr reservation_data);
-
-protected:
+ virtual HostPtr parseInternal(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data);
/// @brief Returns set of the supported parameters for DHCPv6.
///
@@ -149,27 +135,38 @@ protected:
/// @brief Parser for a list of host identifiers.
///
/// This is a parent parser class for parsing "host-reservation-identifiers"
-/// global configuration parmeter. The DHCPv4 and DHCPv6 specific parsers
+/// global configuration parameter. The DHCPv4 and DHCPv6 specific parsers
/// derive from this class.
-class HostReservationIdsParser : public DhcpConfigParser {
+class HostReservationIdsParser : public isc::data::SimpleParser {
public:
/// @brief Constructor.
HostReservationIdsParser();
+ /// @brief Destructor.
+ virtual ~HostReservationIdsParser() { }
+
/// @brief Parses a list of host identifiers.
///
/// @param ids_list Data element pointing to an ordered list of host
/// identifier names.
///
/// @throw DhcpConfigError If specified configuration is invalid.
- virtual void build(isc::data::ConstElementPtr ids_list);
-
- /// @brief Commit, unused.
- virtual void commit() { }
+ void parse(isc::data::ConstElementPtr ids_list);
protected:
+ /// @brief Parses a list of host identifiers.
+ ///
+ /// This method is called by @ref parse and it can be overridden in the
+ /// derived classes to provide class specific parsing logic.
+ ///
+ /// @param ids_list Data element pointing to an ordered list of host
+ /// identifier names.
+ ///
+ /// @throw DhcpConfigError If specified configuration is invalid.
+ virtual void parseInternal(isc::data::ConstElementPtr ids_list);
+
/// @brief Checks if specified identifier name is supported in the
/// context of the parser.
///
diff --git a/src/lib/dhcpsrv/parsers/host_reservations_list_parser.h b/src/lib/dhcpsrv/parsers/host_reservations_list_parser.h
index 86a4895d95..9f6ce2f68f 100644
--- a/src/lib/dhcpsrv/parsers/host_reservations_list_parser.h
+++ b/src/lib/dhcpsrv/parsers/host_reservations_list_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,8 +8,9 @@
#define HOST_RESERVATIONS_LIST_PARSER_H
#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/host.h>
#include <dhcpsrv/subnet_id.h>
-#include <dhcpsrv/parsers/dhcp_config_parser.h>
#include <boost/foreach.hpp>
namespace isc {
@@ -21,39 +22,29 @@ namespace dhcp {
/// parse individual reservations: @c HostReservationParser4 or
/// @c HostReservationParser6.
template<typename HostReservationParserType>
-class HostReservationsListParser : public DhcpConfigParser {
+class HostReservationsListParser : public isc::data::SimpleParser {
public:
- /// @brief Constructor.
+ /// @brief Parses a list of host reservation entries for a subnet.
///
/// @param subnet_id Identifier of the subnet to which the reservations
/// belong.
- HostReservationsListParser(const SubnetID& subnet_id)
- : subnet_id_(subnet_id) {
- }
-
- /// @brief Parses a list of host reservation entries for a subnet.
- ///
/// @param hr_list Data element holding a list of host reservations.
/// Each host reservation is described by a map object.
+ /// @param [out] hosts_list Hosts representing parsed reservations are stored
+ /// in this list.
///
/// @throw DhcpConfigError If the configuration if any of the reservations
/// is invalid.
- virtual void build(isc::data::ConstElementPtr hr_list) {
+ void parse(const SubnetID& subnet_id, isc::data::ConstElementPtr hr_list,
+ HostCollection& hosts_list) {
+ HostCollection hosts;
BOOST_FOREACH(data::ConstElementPtr reservation, hr_list->listValue()) {
- ParserPtr parser(new HostReservationParserType(subnet_id_));
- parser->build(reservation);
+ HostReservationParserType parser;
+ hosts.push_back(parser.parse(subnet_id, reservation));
}
+ hosts_list.swap(hosts);
}
-
- /// @brief Commit, unused.
- virtual void commit() { }
-
-private:
-
- /// @brief Identifier of the subnet to whic the reservations belong.
- SubnetID subnet_id_;
-
};
}
diff --git a/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc b/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc
index b1c9d32f44..87e2fa562d 100644
--- a/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc
+++ b/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -18,82 +18,70 @@ using namespace isc::data;
namespace isc {
namespace dhcp {
-InterfaceListConfigParser::InterfaceListConfigParser(const uint16_t protocol)
- : protocol_(protocol) {
-}
-
void
-InterfaceListConfigParser::build(ConstElementPtr value) {
- CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
-
- BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+IfacesConfigParser::parseInterfacesList(const CfgIfacePtr& cfg_iface,
+ ConstElementPtr ifaces_list) {
+ BOOST_FOREACH(ConstElementPtr iface, ifaces_list->listValue()) {
std::string iface_name = iface->stringValue();
try {
cfg_iface->use(protocol_, iface_name);
} catch (const std::exception& ex) {
isc_throw(DhcpConfigError, "Failed to select interface: "
- << ex.what() << " (" << value->getPosition() << ")");
+ << ex.what() << " (" << iface->getPosition() << ")");
}
}
}
-void
-InterfaceListConfigParser::commit() {
- // Nothing to do.
-}
-
IfacesConfigParser::IfacesConfigParser(const uint16_t protocol)
: protocol_(protocol) {
}
void
-IfacesConfigParser::build(isc::data::ConstElementPtr ifaces_config) {
- BOOST_FOREACH(ConfigPair element, ifaces_config->mapValue()) {
- try {
- if (element.first == "interfaces") {
- InterfaceListConfigParser parser(protocol_);
- parser.build(element.second);
-
- }
-
- } catch (const std::exception& ex) {
- // Append line number where the error occurred.
- isc_throw(DhcpConfigError, ex.what() << " ("
- << element.second->getPosition() << ")");
- }
+IfacesConfigParser::parse(const CfgIfacePtr& cfg,
+ const isc::data::ConstElementPtr& ifaces_config) {
+
+ // Check for re-detect before calling parseInterfacesList()
+ bool re_detect = getBoolean(ifaces_config, "re-detect");
+ cfg->setReDetect(re_detect);
+ if (re_detect) {
+ // Interface clear will drop opened socket information
+ // so close them if the caller did not.
+ IfaceMgr::instance().closeSockets();
+ IfaceMgr::instance().clearIfaces();
+ IfaceMgr::instance().detectIfaces();
}
-}
-
-bool
-IfacesConfigParser::isGenericParameter(const std::string& parameter) const {
- // Currently, the "interfaces" is the only common parameter for
- // DHCPv4 and DHCPv6.
- return (parameter == "interfaces");
-}
-
-IfacesConfigParser4::IfacesConfigParser4()
- : IfacesConfigParser(AF_INET) {
-}
-
-void
-IfacesConfigParser4::build(isc::data::ConstElementPtr ifaces_config) {
- IfacesConfigParser::build(ifaces_config);
- // Get the pointer to the interface configuration.
- CfgIfacePtr cfg = CfgMgr::instance().getStagingCfg()->getCfgIface();
bool socket_type_specified = false;
BOOST_FOREACH(ConfigPair element, ifaces_config->mapValue()) {
try {
- if (element.first == "dhcp-socket-type") {
- cfg->useSocketType(AF_INET, element.second->stringValue());
- socket_type_specified = true;
+ if (element.first == "re-detect") {
+ continue;
+ }
+
+ if (element.first == "interfaces") {
+ parseInterfacesList(cfg, element.second);
+ continue;
+
+ }
- } else if (!isGenericParameter(element.first)) {
- isc_throw(DhcpConfigError, "usupported parameter '"
- << element.first << "'");
+ if (element.first == "dhcp-socket-type") {
+ if (protocol_ == AF_INET) {
+ cfg->useSocketType(AF_INET, element.second->stringValue());
+ socket_type_specified = true;
+ continue;
+ } else {
+ isc_throw(DhcpConfigError,
+ "dhcp-socket-type is not supported in DHCPv6");
+ }
}
+ // This should never happen as the input produced by the parser
+ // see (src/bin/dhcpX/dhcpX_parser.yy) should not produce any
+ // other parameter, so this case is only to catch bugs in
+ // the parser.
+ isc_throw(DhcpConfigError, "unsupported parameter '"
+ << element.first << "'");
} catch (const std::exception& ex) {
// Append line number where the error occurred.
isc_throw(DhcpConfigError, ex.what() << " ("
@@ -102,35 +90,13 @@ IfacesConfigParser4::build(isc::data::ConstElementPtr ifaces_config) {
}
// User hasn't specified the socket type. Log that we are using
- // the default type.
- if (!socket_type_specified) {
+ // the default type. Log it only if this is DHCPv4. (DHCPv6 does not use
+ // raw sockets).
+ if (!socket_type_specified && (protocol_ == AF_INET) ) {
LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_SOCKET_TYPE_DEFAULT)
.arg(cfg->socketTypeToText());
}
}
-IfacesConfigParser6::IfacesConfigParser6()
- : IfacesConfigParser(AF_INET6) {
-}
-
-void
-IfacesConfigParser6::build(isc::data::ConstElementPtr ifaces_config) {
- IfacesConfigParser::build(ifaces_config);
-
- BOOST_FOREACH(ConfigPair element, ifaces_config->mapValue()) {
- try {
- if (!isGenericParameter(element.first)) {
- isc_throw(DhcpConfigError, "usupported parameter '"
- << element.first << "'");
- }
-
- } catch (const std::exception& ex) {
- // Append line number where the error occurred.
- isc_throw(DhcpConfigError, ex.what() << " ("
- << element.second->getPosition() << ")");
- }
- }
-}
-
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/ifaces_config_parser.h b/src/lib/dhcpsrv/parsers/ifaces_config_parser.h
index e4e26430e6..d23a716dc5 100644
--- a/src/lib/dhcpsrv/parsers/ifaces_config_parser.h
+++ b/src/lib/dhcpsrv/parsers/ifaces_config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,147 +8,53 @@
#define IFACES_CONFIG_PARSER_H
#include <cc/data.h>
-#include <dhcpsrv/parsers/dhcp_config_parser.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/cfg_iface.h>
#include <dhcpsrv/parsers/dhcp_parsers.h>
namespace isc {
namespace dhcp {
-/// @brief Parser for interface list definition.
-///
-/// This parser handles Dhcp4/interfaces-config/interfaces and
-/// Dhcp6/interfaces-config/interfaces entries.
-/// It contains a list of network interfaces that the server listens on.
-/// In particular, it can contain an "*" that designates all interfaces.
-class InterfaceListConfigParser : public DhcpConfigParser {
-public:
-
- /// @brief Constructor
- ///
- /// @param protocol AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
- InterfaceListConfigParser(const uint16_t protocol);
-
- /// @brief Parses a list of interface names.
- ///
- /// This method parses a list of interface/address tuples in a text
- /// format. The tuples specify the IP addresses and corresponding
- /// interface names on which the server should listen to the DHCP
- /// messages. The address is optional in each tuple and, if not
- /// specified, the interface name (without slash character) should
- /// be present.
- ///
- /// @param value pointer to the content of parsed values
- ///
- /// @throw DhcpConfigError if the interface names and/or addresses
- /// are invalid.
- virtual void build(isc::data::ConstElementPtr value);
-
- /// @brief Does nothing.
- virtual void commit();
-
-private:
-
- /// @brief AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
- uint16_t protocol_;
-
-};
-
-
/// @brief Parser for the configuration of interfaces.
///
/// This parser parses the "interfaces-config" parameter which holds the
/// full configuration of the DHCP server with respect to the use of
-/// interfaces, sockets and alike.
-///
-/// This parser uses the @c InterfaceListConfigParser to parse the
-/// list of interfaces on which the server should listen. It handles
-/// remaining parameters internally.
+/// interfaces, DHCP traffic sockets and alike.
///
-/// This parser is used as a base for the DHCPv4 and DHCPv6 specific
-/// parsers and should not be used directly.
-class IfacesConfigParser : public DhcpConfigParser {
+/// This parser is used in both DHCPv4 and DHCPv6. Derived parsers
+/// are not needed.
+class IfacesConfigParser : public isc::data::SimpleParser {
public:
/// @brief Constructor
///
/// @param protocol AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
- IfacesConfigParser(const uint16_t protocol);
+ explicit IfacesConfigParser(const uint16_t protocol);
- /// @brief Parses generic parameters in "interfaces-config".
+ /// @brief Parses content of the "interfaces-config".
///
- /// The generic parameters in the "interfaces-config" map are
- /// the ones that are common for DHCPv4 and DHCPv6.
+ /// @param config parsed structures will be stored here
+ /// @param values pointer to the content of parsed values
///
- /// @param ifaces_config A data element holding configuration of
- /// interfaces.
- virtual void build(isc::data::ConstElementPtr ifaces_config);
-
- /// @brief Commit, unused.
- virtual void commit() { }
+ /// @throw DhcpConfigError if the interface names and/or addresses
+ /// are invalid.
+ void parse(const CfgIfacePtr& config, const isc::data::ConstElementPtr& values);
- /// @brief Checks if the specified parameter is a common parameter
- /// for DHCPv4 and DHCPv6 interface configuration.
- ///
- /// This method is invoked by the derived classes to check if the
- /// particular parameter is supported.
+private:
+ /// @brief parses interfaces-list structure
///
- /// @param parameter A name of the parameter.
+ /// This method goes through all the interfaces-specified in
+ /// 'interfaces-list' and enabled them in the specified configuration
+ /// structure
///
- /// @return true if the specified parameter is a common parameter
- /// for DHCPv4 and DHCPv6 server.
- bool isGenericParameter(const std::string& parameter) const;
-
-private:
+ /// @param cfg_iface parsed interfaces will be specified here
+ /// @param ifaces_list interfaces-list to be parsed
+ /// @throw DhcpConfigError if the interface names are invalid.
+ void parseInterfacesList(const CfgIfacePtr& cfg_iface,
+ isc::data::ConstElementPtr ifaces_list);
/// @brief AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
int protocol_;
-
-};
-
-
-/// @brief Parser for the "interfaces-config" parameter of the DHCPv4 server.
-class IfacesConfigParser4 : public IfacesConfigParser {
-public:
-
- /// @brief Constructor.
- ///
- /// Sets the protocol to AF_INET.
- IfacesConfigParser4();
-
- /// @brief Parses DHCPv4 specific parameters.
- ///
- /// Internally it invokes the @c InterfaceConfigParser::build to parse
- /// generic parameters. In addition, it parses the following parameters:
- /// - dhcp-socket-type
- ///
- /// @param ifaces_config A data element holding configuration of
- /// interfaces.
- ///
- /// @throw DhcpConfigError if unsupported parameters is specified.
- virtual void build(isc::data::ConstElementPtr ifaces_config);
-
-};
-
-/// @brief Parser for the "interfaces-config" parameter of the DHCPv4 server.
-class IfacesConfigParser6 : public IfacesConfigParser {
-public:
-
- /// @brief Constructor.
- ///
- /// Sets the protocol to AF_INET6.
- IfacesConfigParser6();
-
- /// @brief Parses DHCPv6 specific parameters.
- ///
- /// Internally it invokes the @c InterfaceConfigParser::build to parse
- /// generic parameters. Currently it doesn't parse any other parameters.
- ///
- /// @param ifaces_config A data element holding configuration of
- /// interfaces.
- ///
- /// @throw DhcpConfigError if unsupported parameters is specified.
- virtual void build(isc::data::ConstElementPtr ifaces_config);
-
};
}
diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.cc b/src/lib/dhcpsrv/parsers/option_data_parser.cc
new file mode 100644
index 0000000000..ec989e0bfe
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/option_data_parser.cc
@@ -0,0 +1,369 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <exceptions/exceptions.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+#include <boost/foreach.hpp>
+#include <limits>
+#include <vector>
+
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+// **************************** OptionDataParser *************************
+
+OptionDataParser::OptionDataParser(const uint16_t address_family)
+ : address_family_(address_family) {
+}
+
+std::pair<OptionDescriptor, std::string>
+OptionDataParser::parse(isc::data::ConstElementPtr single_option) {
+
+ // Try to create the option instance.
+ std::pair<OptionDescriptor, std::string> opt = createOption(single_option);
+
+ if (!opt.first.option_) {
+ isc_throw(isc::InvalidOperation,
+ "parser logic error: no option has been configured and"
+ " thus there is nothing to commit. Has build() been called?");
+ }
+
+ return (opt);
+}
+
+OptionalValue<uint32_t>
+OptionDataParser::extractCode(ConstElementPtr parent) const {
+ uint32_t code;
+ try {
+ code = getInteger(parent, "code");
+
+ } catch (const std::exception&) {
+ // The code parameter was not found. Return an unspecified
+ // value.
+ return (OptionalValue<uint32_t>());
+ }
+
+ if (code == 0) {
+ isc_throw(DhcpConfigError, "option code must not be zero "
+ "(" << getPosition("code", parent) << ")");
+
+ } else if (address_family_ == AF_INET &&
+ code > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << code
+ << "', it must not be greater than '"
+ << static_cast<int>(std::numeric_limits<uint8_t>::max())
+ << "' (" << getPosition("code", parent)
+ << ")");
+
+ } else if (address_family_ == AF_INET6 &&
+ code > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << code
+ << "', it must not exceed '"
+ << std::numeric_limits<uint16_t>::max()
+ << "' (" << getPosition("code", parent)
+ << ")");
+
+ }
+
+ return (OptionalValue<uint32_t>(code, OptionalValueState(true)));
+}
+
+OptionalValue<std::string>
+OptionDataParser::extractName(ConstElementPtr parent) const {
+ std::string name;
+ try {
+ name = getString(parent, "name");
+
+ } catch (...) {
+ return (OptionalValue<std::string>());
+ }
+
+ if (name.find(" ") != std::string::npos) {
+ isc_throw(DhcpConfigError, "invalid option name '" << name
+ << "', space character is not allowed ("
+ << getPosition("name", parent) << ")");
+ }
+
+ return (OptionalValue<std::string>(name, OptionalValueState(true)));
+}
+
+std::string
+OptionDataParser::extractData(ConstElementPtr parent) const {
+ std::string data;
+ try {
+ data = getString(parent, "data");
+
+ } catch (...) {
+ // The "data" parameter was not found. Return an empty value.
+ return (data);
+ }
+
+ return (data);
+}
+
+OptionalValue<bool>
+OptionDataParser::extractCSVFormat(ConstElementPtr parent) const {
+ bool csv_format = true;
+ try {
+ csv_format = getBoolean(parent, "csv-format");
+
+ } catch (...) {
+ return (OptionalValue<bool>(csv_format));
+ }
+
+ return (OptionalValue<bool>(csv_format, OptionalValueState(true)));
+}
+
+std::string
+OptionDataParser::extractSpace(ConstElementPtr parent) const {
+ std::string space = address_family_ == AF_INET ?
+ DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE;
+ try {
+ space = getString(parent, "space");
+
+ } catch (...) {
+ return (space);
+ }
+
+ try {
+ if (!OptionSpace::validateName(space)) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ }
+
+ if ((space == DHCP4_OPTION_SPACE) && (address_family_ == AF_INET6)) {
+ isc_throw(DhcpConfigError, "'" << DHCP4_OPTION_SPACE
+ << "' option space name is reserved for DHCPv4 server");
+
+ } else if ((space == DHCP6_OPTION_SPACE) &&
+ (address_family_ == AF_INET)) {
+ isc_throw(DhcpConfigError, "'" << DHCP6_OPTION_SPACE
+ << "' option space name is reserved for DHCPv6 server");
+ }
+
+ } catch (std::exception& ex) {
+ // Append position of the option space parameter.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << getPosition("space", parent) << ")");
+ }
+
+ return (space);
+}
+
+OptionalValue<bool>
+OptionDataParser::extractPersistent(ConstElementPtr parent) const {
+ bool persist = false;
+ try {
+ persist = getBoolean(parent, "always-send");
+
+ } catch (...) {
+ return (OptionalValue<bool>(persist));
+ }
+
+ return (OptionalValue<bool>(persist, OptionalValueState(true)));
+}
+
+template<typename SearchKey>
+OptionDefinitionPtr
+OptionDataParser::findOptionDefinition(const std::string& option_space,
+ const SearchKey& search_key) const {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(option_space, search_key);
+
+ if (!def) {
+ // Check if this is a vendor-option. If it is, get vendor-specific
+ // definition.
+ uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
+ if (vendor_id) {
+ const Option::Universe u = address_family_ == AF_INET ?
+ Option::V4 : Option::V6;
+ def = LibDHCP::getVendorOptionDef(u, vendor_id, search_key);
+ }
+ }
+
+ if (!def) {
+ // Check if this is an option specified by a user. We used to
+ // check that in the staging configuration, but when the configuration
+ // changes are caused by a command the staging configuration doesn't
+ // exist. What is always available is the container holding runtime
+ // option definitions in LibDHCP. It holds option definitions from
+ // the staging configuration in case of the full reconfiguration or
+ // the definitions from the current configuration in case there is
+ // no staging configuration (after configuration commit). In other
+ // words, runtime options are always the ones that we need here.
+ def = LibDHCP::getRuntimeOptionDef(option_space, search_key);
+ }
+
+ return (def);
+}
+
+std::pair<OptionDescriptor, std::string>
+OptionDataParser::createOption(ConstElementPtr option_data) {
+ const Option::Universe universe = address_family_ == AF_INET ?
+ Option::V4 : Option::V6;
+
+ OptionalValue<uint32_t> code_param = extractCode(option_data);
+ OptionalValue<std::string> name_param = extractName(option_data);
+ OptionalValue<bool> csv_format_param = extractCSVFormat(option_data);
+ OptionalValue<bool> persist_param = extractPersistent(option_data);
+ std::string data_param = extractData(option_data);
+ std::string space_param = extractSpace(option_data);
+
+ // Require that option code or option name is specified.
+ if (!code_param.isSpecified() && !name_param.isSpecified()) {
+ isc_throw(DhcpConfigError, "option data configuration requires one of"
+ " 'code' or 'name' parameters to be specified"
+ << " (" << option_data->getPosition() << ")");
+ }
+
+ // Try to find a corresponding option definition using option code or
+ // option name.
+ OptionDefinitionPtr def = code_param.isSpecified() ?
+ findOptionDefinition(space_param, code_param) :
+ findOptionDefinition(space_param, name_param);
+
+ // If there is no definition, the user must not explicitly enable the
+ // use of csv-format.
+ if (!def) {
+ // If explicitly requested that the CSV format is to be used,
+ // the option definition is a must.
+ if (csv_format_param.isSpecified() && csv_format_param) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << space_param << "." << name_param
+ << "' having code '" << code_param
+ << "' does not exist ("
+ << getPosition("name", option_data)
+ << ")");
+
+ // If there is no option definition and the option code is not specified
+ // we have no means to find the option code.
+ } else if (name_param.isSpecified() && !code_param.isSpecified()) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << space_param << "." << name_param
+ << "' does not exist ("
+ << getPosition("name", option_data)
+ << ")");
+ }
+ }
+
+ // Transform string of hexadecimal digits into binary format.
+ std::vector<uint8_t> binary;
+ std::vector<std::string> data_tokens;
+
+ // If the definition is available and csv-format hasn't been explicitly
+ // disabled, we will parse the data as comma separated values.
+ if (def && (!csv_format_param.isSpecified() || csv_format_param)) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ // It is the only usage of the escape option: this allows
+ // to embed commas in individual values and to return
+ // for instance a string value with embedded commas.
+ data_tokens = isc::util::str::tokens(data_param, ",", true);
+
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ // The decodeHex function expects that the string contains an
+ // even number of digits. If we don't meet this requirement,
+ // we have to insert a leading 0.
+ if (!data_param.empty() && ((data_param.length() % 2) != 0)) {
+ data_param = data_param.insert(0, "0");
+ }
+ util::encode::decodeHex(data_param, binary);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "option data is not a valid"
+ << " string of hexadecimal digits: " << data_param
+ << " ("
+ << getPosition("data", option_data)
+ << ")");
+ }
+ }
+
+ OptionPtr option;
+ OptionDescriptor desc(false);
+
+ if (!def) {
+ // @todo We have a limited set of option definitions initialized at
+ // the moment. In the future we want to initialize option definitions
+ // for all options. Consequently an error will be issued if an option
+ // definition does not exist for a particular option code. For now it is
+ // ok to create generic option if definition does not exist.
+ OptionPtr option(new Option(universe, static_cast<uint16_t>(code_param),
+ binary));
+
+ desc.option_ = option;
+ desc.persistent_ = persist_param.isSpecified() && persist_param;
+ } else {
+
+ // Option name is specified it should match the name in the definition.
+ if (name_param.isSpecified() && (def->getName() != name_param.get())) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << name_param << "' does not match the "
+ << "option definition: '" << space_param
+ << "." << def->getName() << "' ("
+ << getPosition("name", option_data)
+ << ")");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
+ try {
+ bool use_csv = !csv_format_param.isSpecified() || csv_format_param;
+ OptionPtr option = use_csv ?
+ def->optionFactory(universe, def->getCode(), data_tokens) :
+ def->optionFactory(universe, def->getCode(), binary);
+ desc.option_ = option;
+ desc.persistent_ = persist_param.isSpecified() && persist_param;
+ if (use_csv) {
+ desc.formatted_value_ = data_param;
+ }
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << space_param
+ << ", code: " << def->getCode() << "): "
+ << ex.what() << " ("
+ << getPosition("data", option_data)
+ << ")");
+ }
+ }
+
+ // All went good, so we can set the option space name.
+ return make_pair(desc, space_param);
+}
+
+// **************************** OptionDataListParser *************************
+OptionDataListParser::OptionDataListParser(//const std::string&,
+ //const CfgOptionPtr& cfg,
+ const uint16_t address_family)
+ : address_family_(address_family) {
+}
+
+
+void OptionDataListParser::parse(const CfgOptionPtr& cfg,
+ isc::data::ConstElementPtr option_data_list) {
+ OptionDataParser option_parser(address_family_);
+ BOOST_FOREACH(ConstElementPtr data, option_data_list->listValue()) {
+ std::pair<OptionDescriptor, std::string> option =
+ option_parser.parse(data);
+ // Use the option description to keep the formatted value
+ cfg->add(option.first, option.second);
+ cfg->encapsulate();
+ }
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.h b/src/lib/dhcpsrv/parsers/option_data_parser.h
new file mode 100644
index 0000000000..b4fefeb65e
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/option_data_parser.h
@@ -0,0 +1,182 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef OPTION_DATA_PARSER_H
+#define OPTION_DATA_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/cfg_option.h>
+#include <util/optional_value.h>
+#include <cstdint>
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for option data value.
+///
+/// This parser parses configuration entries that specify value of
+/// a single option. These entries include option name, option code
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
+class OptionDataParser : public isc::data::SimpleParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param address_family Address family: @c AF_INET or @c AF_INET6.
+ explicit OptionDataParser(const uint16_t address_family);
+
+ /// @brief Parses ElementPtr containing option definition
+ ///
+ /// This method parses ElementPtr containing the option definition,
+ /// instantiates the option for it and then returns a pair
+ /// of option descriptor (that holds that new option) and
+ /// a string that specifies the option space.
+ ///
+ /// Note: ElementPtr is expected to contain all fields. If your
+ /// ElementPtr does not have them, please use
+ /// @ref isc::data::SimpleParser::setDefaults to fill the missing fields
+ /// with default values.
+ ///
+ /// @param single_option ElementPtr containing option definition
+ /// @return Option object wrapped in option description and an option
+ /// space
+ std::pair<OptionDescriptor, std::string>
+ parse(isc::data::ConstElementPtr single_option);
+private:
+
+ /// @brief Finds an option definition within an option space
+ ///
+ /// Given an option space and an option code, find the corresponding
+ /// option definition within the option definition storage.
+ ///
+ /// @param option_space name of the parameter option space
+ /// @param search_key an option code or name to be used to lookup the
+ /// option definition.
+ /// @tparam A numeric type for searching using an option code or the
+ /// string for searching using the option name.
+ ///
+ /// @return OptionDefinitionPtr of the option definition or an
+ /// empty OptionDefinitionPtr if not found.
+ /// @throw DhcpConfigError if the option space requested is not valid
+ /// for this server.
+ template<typename SearchKey>
+ OptionDefinitionPtr findOptionDefinition(const std::string& option_space,
+ const SearchKey& search_key) const;
+
+ /// @brief Create option instance.
+ ///
+ /// Creates an instance of an option and adds it to the provided
+ /// options storage. If the option data parsed by \ref build function
+ /// are invalid or insufficient this function emits an exception.
+ ///
+ /// @param option_data An element holding data for a single option being
+ /// created.
+ ///
+ /// @return created option descriptor
+ ///
+ /// @throw DhcpConfigError if parameters provided in the configuration
+ /// are invalid.
+ std::pair<OptionDescriptor, std::string>
+ createOption(isc::data::ConstElementPtr option_data);
+
+ /// @brief Retrieves parsed option code as an optional value.
+ ///
+ /// @param parent A data element holding full option data configuration.
+ ///
+ /// @return Option code, possibly unspecified.
+ /// @throw DhcpConfigError if option code is invalid.
+ util::OptionalValue<uint32_t>
+ extractCode(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves parsed option name as an optional value.
+ ///
+ /// @param parent A data element holding full option data configuration.
+ ///
+ /// @return Option name, possibly unspecified.
+ /// @throw DhcpConfigError if option name is invalid.
+ util::OptionalValue<std::string>
+ extractName(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves csv-format parameter as an optional value.
+ ///
+ /// @return Value of the csv-format parameter, possibly unspecified.
+ util::OptionalValue<bool> extractCSVFormat(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves option data as a string.
+ ///
+ /// @param parent A data element holding full option data configuration.
+ /// @return Option data as a string. It will return empty string if
+ /// option data is unspecified.
+ std::string extractData(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves option space name.
+ ///
+ /// If option space name is not specified in the configuration the
+ /// 'dhcp4' or 'dhcp6' option space name is returned, depending on
+ /// the universe specified in the parser context.
+ ///
+ /// @param parent A data element holding full option data configuration.
+ ///
+ /// @return Option space name.
+ std::string extractSpace(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves persistent/always-send parameter as an optional value.
+ ///
+ /// @return Value of the persistent parameter, possibly unspecified.
+ util::OptionalValue<bool> extractPersistent(data::ConstElementPtr parent) const;
+
+ /// @brief Address family: @c AF_INET or @c AF_INET6.
+ uint16_t address_family_;
+};
+
+/// @brief Parser for option data values within a subnet.
+///
+/// This parser iterates over all entries that define options
+/// data for a particular subnet and creates a collection of options.
+/// If parsing is successful, all these options are added to the Subnet
+/// object.
+class OptionDataListParser : public isc::data::SimpleParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param address_family Address family: @c AF_INET or AF_INET6
+ explicit OptionDataListParser(const uint16_t address_family);
+
+ /// @brief Parses a list of options, instantiates them and stores in cfg
+ ///
+ /// This method expects to get a list of options in option_data_list,
+ /// iterates over them, creates option objects, wraps them with
+ /// option descriptor and stores in specified cfg.
+ ///
+ /// @param cfg created options will be stored here
+ /// @param option_data_list configuration that describes the options
+ void parse(const CfgOptionPtr& cfg,
+ isc::data::ConstElementPtr option_data_list);
+private:
+ /// @brief Address family: @c AF_INET or @c AF_INET6
+ uint16_t address_family_;
+};
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // OPTION_DATA_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.cc b/src/lib/dhcpsrv/parsers/simple_parser4.cc
new file mode 100644
index 0000000000..e3a033ea07
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/simple_parser4.cc
@@ -0,0 +1,155 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <cc/data.h>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+/// @brief This sets of arrays define the default values and
+/// values inherited (derived) between various scopes.
+///
+/// Each of those is documented in @file simple_parser4.cc. This
+/// is different than most other comments in Kea code. The reason
+/// for placing those in .cc rather than .h file is that it
+/// is expected to be one centralized place to look at for
+/// the default values. This is expected to be looked at also by
+/// people who are not skilled in C or C++, so they may be
+/// confused with the differences between declaration and definition.
+/// As such, there's one file to look at that hopefully is readable
+/// without any C or C++ skills.
+///
+/// @{
+
+/// @brief This table defines default values for option definitions in DHCPv4.
+///
+/// Dhcp4 may contain an array called option-def that enumerates new option
+/// definitions. This array lists default values for those option definitions.
+const SimpleDefaults SimpleParser4::OPTION4_DEF_DEFAULTS = {
+ { "record-types", Element::string, ""},
+ { "space", Element::string, "dhcp4"},
+ { "array", Element::boolean, "false"},
+ { "encapsulate", Element::string, "" }
+};
+
+/// @brief This table defines default values for options in DHCPv4.
+///
+/// Dhcp4 usually contains option values (option-data) defined in global,
+/// subnet, class or host reservations scopes. This array lists default values
+/// for those option-data declarations.
+const SimpleDefaults SimpleParser4::OPTION4_DEFAULTS = {
+ { "space", Element::string, "dhcp4"},
+ { "csv-format", Element::boolean, "true"},
+ { "always-send", Element::boolean, "false"}
+};
+
+/// @brief This table defines default global values for DHCPv4
+///
+/// Some of the global parameters defined in the global scope (i.e. directly
+/// in Dhcp4) are optional. If not defined, the following values will be
+/// used.
+const SimpleDefaults SimpleParser4::GLOBAL4_DEFAULTS = {
+ { "renew-timer", Element::integer, "900" },
+ { "rebind-timer", Element::integer, "1800" },
+ { "valid-lifetime", Element::integer, "7200" },
+ { "decline-probation-period", Element::integer, "86400" }, // 24h
+ { "dhcp4o6-port", Element::integer, "0" },
+ { "echo-client-id", Element::boolean, "true" },
+ { "match-client-id", Element::boolean, "true" },
+ { "next-server", Element::string, "0.0.0.0" }
+};
+
+/// @brief This table defines default values for each IPv4 subnet.
+const SimpleDefaults SimpleParser4::SUBNET4_DEFAULTS = {
+ { "id", Element::integer, "0" }, // 0 means autogenerate
+ { "interface", Element::string, "" },
+ { "client-class", Element::string, "" },
+ { "reservation-mode", Element::string, "all" },
+ { "4o6-interface", Element::string, "" },
+ { "4o6-interface-id", Element::string, "" },
+ { "4o6-subnet", Element::string, "" },
+};
+
+/// @brief This table defines default values for interfaces for DHCPv4.
+const SimpleDefaults SimpleParser4::IFACE4_DEFAULTS = {
+ { "re-detect", Element::boolean, "true" }
+};
+
+/// @brief List of parameters that can be inherited from the global to subnet4 scope.
+///
+/// Some parameters may be defined on both global (directly in Dhcp4) and
+/// subnet (Dhcp4/subnet4/...) scope. If not defined in the subnet scope,
+/// the value is being inherited (derived) from the global scope. This
+/// array lists all of such parameters.
+const ParamsList SimpleParser4::INHERIT_GLOBAL_TO_SUBNET4 = {
+ "renew-timer",
+ "rebind-timer",
+ "valid-lifetime",
+ "match-client-id",
+ "next-server"
+};
+/// @}
+
+/// ---------------------------------------------------------------------------
+/// --- end of default values -------------------------------------------------
+/// ---------------------------------------------------------------------------
+
+size_t SimpleParser4::setAllDefaults(isc::data::ElementPtr global) {
+ size_t cnt = 0;
+
+ // Set global defaults first.
+ cnt = setDefaults(global, GLOBAL4_DEFAULTS);
+
+ // Now set option definition defaults for each specified option definition
+ ConstElementPtr option_defs = global->get("option-def");
+ if (option_defs) {
+ BOOST_FOREACH(ElementPtr option_def, option_defs->listValue()) {
+ cnt += SimpleParser::setDefaults(option_def, OPTION4_DEF_DEFAULTS);
+ }
+ }
+
+ // Set the defaults for option data
+ ConstElementPtr options = global->get("option-data");
+ if (options) {
+ cnt += setListDefaults(options, OPTION4_DEFAULTS);
+ }
+
+ // Now set the defaults for defined subnets
+ ConstElementPtr subnets = global->get("subnet4");
+ if (subnets) {
+ cnt += setListDefaults(subnets, SUBNET4_DEFAULTS);
+ }
+
+ // Set the defaults for interfaces config
+ ConstElementPtr ifaces_cfg = global->get("interfaces-config");
+ if (ifaces_cfg) {
+ ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(ifaces_cfg);
+ cnt += setDefaults(mutable_cfg, IFACE4_DEFAULTS);
+ }
+
+ return (cnt);
+}
+
+size_t SimpleParser4::deriveParameters(isc::data::ElementPtr global) {
+ size_t cnt = 0;
+
+ // Now derive global parameters into subnets.
+ ConstElementPtr subnets = global->get("subnet4");
+ if (subnets) {
+ BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
+ cnt += SimpleParser::deriveParams(global, single_subnet,
+ INHERIT_GLOBAL_TO_SUBNET4);
+ }
+ }
+
+ return (cnt);
+}
+
+};
+};
diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.h b/src/lib/dhcpsrv/parsers/simple_parser4.h
new file mode 100644
index 0000000000..4e73e31204
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/simple_parser4.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SIMPLE_PARSER4_H
+#define SIMPLE_PARSER4_H
+
+#include <cc/simple_parser.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief SimpleParser specialized for DHCPv4
+///
+/// This class is a @ref isc::data::SimpleParser dedicated to DHCPv4 parser.
+/// In particular, it contains all the default values and names of the
+/// parameters that are to be derived (inherited) between scopes.
+/// For the actual values, see @file simple_parser4.cc
+class SimpleParser4 : public isc::data::SimpleParser {
+public:
+ /// @brief Sets all defaults for DHCPv4 configuration
+ ///
+ /// This method sets global, option data and option definitions defaults.
+ ///
+ /// @param global scope to be filled in with defaults.
+ /// @return number of default values added
+ static size_t setAllDefaults(isc::data::ElementPtr global);
+
+ /// @brief Derives (inherits) all parameters from global to more specific scopes.
+ ///
+ /// This method currently does the following:
+ /// - derives global parameters to subnets (lifetimes for now)
+ /// @param global scope to be modified if needed (subnet4 will be extracted)
+ /// @return number of default values derived
+ static size_t deriveParameters(isc::data::ElementPtr global);
+
+ // see simple_parser4.cc for comments for those parameters
+ static const isc::data::SimpleDefaults OPTION4_DEF_DEFAULTS;
+ static const isc::data::SimpleDefaults OPTION4_DEFAULTS;
+ static const isc::data::SimpleDefaults GLOBAL4_DEFAULTS;
+ static const isc::data::SimpleDefaults SUBNET4_DEFAULTS;
+ static const isc::data::SimpleDefaults IFACE4_DEFAULTS;
+ static const isc::data::ParamsList INHERIT_GLOBAL_TO_SUBNET4;
+};
+
+};
+};
+#endif
diff --git a/src/lib/dhcpsrv/parsers/simple_parser6.cc b/src/lib/dhcpsrv/parsers/simple_parser6.cc
new file mode 100644
index 0000000000..16e0d801b7
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/simple_parser6.cc
@@ -0,0 +1,153 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <cc/data.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+/// @brief This sets of arrays define the default values and
+/// values inherited (derived) between various scopes.
+///
+/// Each of those is documented in @file simple_parser6.cc. This
+/// is different than most other comments in Kea code. The reason
+/// for placing those in .cc rather than .h file is that it
+/// is expected to be one centralized place to look at for
+/// the default values. This is expected to be looked at also by
+/// people who are not skilled in C or C++, so they may be
+/// confused with the differences between declaration and definition.
+/// As such, there's one file to look at that hopefully is readable
+/// without any C or C++ skills.
+///
+/// @{
+
+/// @brief This table defines default values for option definitions in DHCPv6.
+///
+/// Dhcp6 may contain an array called option-def that enumerates new option
+/// definitions. This array lists default values for those option definitions.
+const SimpleDefaults SimpleParser6::OPTION6_DEF_DEFAULTS = {
+ { "record-types", Element::string, ""},
+ { "space", Element::string, "dhcp6"},
+ { "array", Element::boolean, "false"},
+ { "encapsulate", Element::string, "" }
+};
+
+/// @brief This table defines default values for options in DHCPv6.
+///
+/// Dhcp6 usually contains option values (option-data) defined in global,
+/// subnet, class or host reservations scopes. This array lists default values
+/// for those option-data declarations.
+const SimpleDefaults SimpleParser6::OPTION6_DEFAULTS = {
+ { "space", Element::string, "dhcp6"},
+ { "csv-format", Element::boolean, "true"},
+ { "always-send", Element::boolean, "false"}
+};
+
+/// @brief This table defines default global values for DHCPv6
+///
+/// Some of the global parameters defined in the global scope (i.e. directly
+/// in Dhcp6) are optional. If not defined, the following values will be
+/// used.
+const SimpleDefaults SimpleParser6::GLOBAL6_DEFAULTS = {
+ { "renew-timer", Element::integer, "900" },
+ { "rebind-timer", Element::integer, "1800" },
+ { "preferred-lifetime", Element::integer, "3600" },
+ { "valid-lifetime", Element::integer, "7200" },
+ { "decline-probation-period", Element::integer, "86400" }, // 24h
+ { "dhcp4o6-port", Element::integer, "0" }
+};
+
+/// @brief This table defines default values for each IPv6 subnet.
+const SimpleDefaults SimpleParser6::SUBNET6_DEFAULTS = {
+ { "id", Element::integer, "0" }, // 0 means autogenerate
+ { "interface", Element::string, "" },
+ { "client-class", Element::string, "" },
+ { "reservation-mode", Element::string, "all" },
+ { "rapid-commit", Element::boolean, "false" }, // rapid-commit disabled by default
+ { "interface-id", Element::string, "" },
+};
+
+/// @brief This table defines default values for interfaces for DHCPv6.
+const SimpleDefaults SimpleParser6::IFACE6_DEFAULTS = {
+ { "re-detect", Element::boolean, "true" }
+};
+
+/// @brief List of parameters that can be inherited from the global to subnet6 scope.
+///
+/// Some parameters may be defined on both global (directly in Dhcp6) and
+/// subnet (Dhcp6/subnet6/...) scope. If not defined in the subnet scope,
+/// the value is being inherited (derived) from the global scope. This
+/// array lists all of such parameters.
+const ParamsList SimpleParser6::INHERIT_GLOBAL_TO_SUBNET6 = {
+ "renew-timer",
+ "rebind-timer",
+ "preferred-lifetime",
+ "valid-lifetime"
+};
+/// @}
+
+/// ---------------------------------------------------------------------------
+/// --- end of default values -------------------------------------------------
+/// ---------------------------------------------------------------------------
+
+size_t SimpleParser6::setAllDefaults(isc::data::ElementPtr global) {
+ size_t cnt = 0;
+
+ // Set global defaults first.
+ cnt = setDefaults(global, GLOBAL6_DEFAULTS);
+
+ // Now set the defaults for each specified option definition
+ ConstElementPtr option_defs = global->get("option-def");
+ if (option_defs) {
+ BOOST_FOREACH(ElementPtr option_def, option_defs->listValue()) {
+ cnt += SimpleParser::setDefaults(option_def, OPTION6_DEF_DEFAULTS);
+ }
+ }
+
+ // Set the defaults for option data
+ ConstElementPtr options = global->get("option-data");
+ if (options) {
+ BOOST_FOREACH(ElementPtr single_option, options->listValue()) {
+ cnt += SimpleParser::setDefaults(single_option, OPTION6_DEFAULTS);
+ }
+ }
+
+ // Now set the defaults for defined subnets
+ // Now set the defaults for defined subnets
+ ConstElementPtr subnets = global->get("subnet6");
+ if (subnets) {
+ cnt += setListDefaults(subnets, SUBNET6_DEFAULTS);
+ }
+
+ // Set the defaults for interfaces config
+ ConstElementPtr ifaces_cfg = global->get("interfaces-config");
+ if (ifaces_cfg) {
+ ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(ifaces_cfg);
+ cnt += setDefaults(mutable_cfg, IFACE6_DEFAULTS);
+ }
+
+ return (cnt);
+}
+
+size_t SimpleParser6::deriveParameters(isc::data::ElementPtr global) {
+ size_t cnt = 0;
+ // Now derive global parameters into subnets.
+ ConstElementPtr subnets = global->get("subnet6");
+ if (subnets) {
+ BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
+ cnt += SimpleParser::deriveParams(global, single_subnet,
+ INHERIT_GLOBAL_TO_SUBNET6);
+ }
+ }
+
+ return (cnt);
+}
+
+};
+};
diff --git a/src/lib/dhcpsrv/parsers/simple_parser6.h b/src/lib/dhcpsrv/parsers/simple_parser6.h
new file mode 100644
index 0000000000..755f0c2b86
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/simple_parser6.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SIMPLE_PARSER6_H
+#define SIMPLE_PARSER6_H
+
+#include <cc/simple_parser.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief SimpleParser specialized for DHCPv6
+///
+/// This class is a @ref isc::data::SimpleParser dedicated to DHCPv6 parser.
+/// In particular, it contains all the default values and names of the
+/// parameters that are to be derived (inherited) between scopes.
+/// For the actual values, see @file simple_parser6.cc
+class SimpleParser6 : public isc::data::SimpleParser {
+public:
+
+ /// @brief Sets all defaults for DHCPv6 configuration
+ ///
+ /// This method sets global, option data and option definitions defaults.
+ ///
+ /// @param global scope to be filled in with defaults.
+ /// @return number of default values added
+ static size_t setAllDefaults(isc::data::ElementPtr global);
+
+ /// @brief Derives (inherits) all parameters from global to more specific scopes.
+ ///
+ /// This method currently does the following:
+ /// - derives global parameters to subnets (lifetimes for now)
+ /// @param global scope to be modified if needed (subnet4 will be extracted)
+ /// @return number of default values derived
+ static size_t deriveParameters(isc::data::ElementPtr global);
+
+ // see simple_parser6.cc for comments for those parameters
+ static const isc::data::SimpleDefaults OPTION6_DEF_DEFAULTS;
+ static const isc::data::SimpleDefaults OPTION6_DEFAULTS;
+ static const isc::data::SimpleDefaults GLOBAL6_DEFAULTS;
+ static const isc::data::SimpleDefaults SUBNET6_DEFAULTS;
+ static const isc::data::SimpleDefaults IFACE6_DEFAULTS;
+ static const isc::data::ParamsList INHERIT_GLOBAL_TO_SUBNET6;
+};
+
+};
+};
+
+#endif
diff --git a/src/lib/dhcpsrv/pgsql_connection.cc b/src/lib/dhcpsrv/pgsql_connection.cc
index e79712c2fa..0e66d6e558 100644
--- a/src/lib/dhcpsrv/pgsql_connection.cc
+++ b/src/lib/dhcpsrv/pgsql_connection.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -19,7 +19,7 @@
// #define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3','5','0','5')
//
// PostgreSQL deliberately omits the MAKE_SQLSTATE macro so callers can/must
-// supply their own. We'll define it as an initlizer_list:
+// supply their own. We'll define it as an initialization list:
#define MAKE_SQLSTATE(ch1,ch2,ch3,ch4,ch5) {ch1,ch2,ch3,ch4,ch5}
// So we can use it like this: const char some_error[] = ERRCODE_xxxx;
#define PGSQL_STATECODE_LEN 5
@@ -31,11 +31,13 @@ namespace isc {
namespace dhcp {
// Default connection timeout
+
+/// @todo: migrate this default timeout to src/bin/dhcpX/simple_parserX.cc
const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
-PgSqlResult::PgSqlResult(PGresult *result)
+PgSqlResult::PgSqlResult(PGresult *result)
: result_(result), rows_(0), cols_(0) {
if (!result) {
isc_throw (BadValue, "PgSqlResult result pointer cannot be null");
@@ -45,10 +47,10 @@ PgSqlResult::PgSqlResult(PGresult *result)
cols_ = PQnfields(result);
}
-void
+void
PgSqlResult::rowCheck(int row) const {
if (row < 0 || row >= rows_) {
- isc_throw (DbOperationError, "row: " << row
+ isc_throw (DbOperationError, "row: " << row
<< ", out of range: 0.." << rows_);
}
}
@@ -151,6 +153,40 @@ PgSqlConnection::openDatabase() {
dbconnparameters += "host = '" + shost + "'" ;
+ string sport;
+ try {
+ sport = getParameter("port");
+ } catch (...) {
+ // No port parameter, we are going to use the default port.
+ sport = "";
+ }
+
+ if (sport.size() > 0) {
+ unsigned int port = 0;
+
+ // Port was given, so try to convert it to an integer.
+ try {
+ port = boost::lexical_cast<unsigned int>(sport);
+ } catch (...) {
+ // Port given but could not be converted to an unsigned int.
+ // Just fall back to the default value.
+ port = 0;
+ }
+
+ // The port is only valid when it is in the 0..65535 range.
+ // Again fall back to the default when the given value is invalid.
+ if (port > numeric_limits<uint16_t>::max()) {
+ port = 0;
+ }
+
+ // Add it to connection parameters when not default.
+ if (port > 0) {
+ std::ostringstream oss;
+ oss << port;
+ dbconnparameters += " port = " + oss.str();
+ }
+ }
+
string suser;
try {
suser = getParameter("user");
@@ -198,7 +234,7 @@ PgSqlConnection::openDatabase() {
}
// The timeout is only valid if greater than zero, as depending on the
- // database, a zero timeout might signify someting like "wait
+ // database, a zero timeout might signify something like "wait
// indefinitely".
//
// The check below also rejects a value greater than the maximum
@@ -239,7 +275,7 @@ PgSqlConnection::openDatabase() {
bool
PgSqlConnection::compareError(const PgSqlResult& r, const char* error_state) {
const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
- // PostgreSQL garuantees it will always be 5 characters long
+ // PostgreSQL guarantees it will always be 5 characters long
return ((sqlstate != NULL) &&
(memcmp(sqlstate, error_state, PGSQL_STATECODE_LEN) == 0));
}
diff --git a/src/lib/dhcpsrv/pgsql_connection.h b/src/lib/dhcpsrv/pgsql_connection.h
index 8e7fe884c8..35e1644d64 100644
--- a/src/lib/dhcpsrv/pgsql_connection.h
+++ b/src/lib/dhcpsrv/pgsql_connection.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12,18 +12,22 @@
#include <boost/scoped_ptr.hpp>
#include <vector>
-
+#include <stdint.h>
namespace isc {
namespace dhcp {
+/// @brief Define PostgreSQL backend version: 3.0
+const uint32_t PG_SCHEMA_VERSION_MAJOR = 3;
+const uint32_t PG_SCHEMA_VERSION_MINOR = 1;
+
// Maximum number of parameters that can be used a statement
// @todo This allows us to use an initializer list (since we can't
// require C++11). It's unlikely we'd go past this many a single
// statement.
const size_t PGSQL_MAX_PARAMETERS_IN_QUERY = 32;
-/// @brief Define a PostgreSQL statement
+/// @brief Define a PostgreSQL statement.
///
/// Each statement is associated with an index, which is used to reference the
/// associated prepared statement.
@@ -47,8 +51,9 @@ struct PgSqlTaggedStatement {
/// @{
/// @brief Constants for PostgreSQL data types
-/// This are defined by PostgreSQL in <catalog/pg_type.h>, but including
+/// These are defined by PostgreSQL in <catalog/pg_type.h>, but including
/// this file is extraordinarily convoluted, so we'll use these to fill-in.
+/// @{
const size_t OID_NONE = 0; // PostgreSQL infers proper type
const size_t OID_BOOL = 16;
const size_t OID_BYTEA = 17;
@@ -60,7 +65,7 @@ const size_t OID_VARCHAR = 1043;
const size_t OID_TIMESTAMP = 1114;
/// @}
-/// @brief RAII wrapper for Posgtresql Result sets
+/// @brief RAII wrapper for PostgreSQL Result sets
///
/// When a Postgresql statement is executed, the results are returned
/// in pointer allocated structure, PGresult*. Data and status information
diff --git a/src/lib/dhcpsrv/pgsql_exchange.cc b/src/lib/dhcpsrv/pgsql_exchange.cc
index 259dfadbcb..f643f02ed2 100644
--- a/src/lib/dhcpsrv/pgsql_exchange.cc
+++ b/src/lib/dhcpsrv/pgsql_exchange.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -57,8 +57,8 @@ void PsqlBindArray::add(const bool& value) {
}
void PsqlBindArray::add(const uint8_t& byte) {
- // We static_cast to an unsigned int, otherwise lexcial_cast may to
- // treat byte as a character, which yields "" for unprintable values
+ // We static_cast to an unsigned int, otherwise lexical_cast may to
+ // treat byte as a character, which yields "" for unprintable values
addTempString(boost::lexical_cast<std::string>
(static_cast<unsigned int>(byte)));
}
@@ -168,8 +168,8 @@ PgSqlExchange::getRawColumnValue(const PgSqlResult& r, const int row,
return (value);
}
-bool
-PgSqlExchange::isColumnNull(const PgSqlResult& r, const int row,
+bool
+PgSqlExchange::isColumnNull(const PgSqlResult& r, const int row,
const size_t col) {
r.rowColCheck(row,col);
return (PQgetisnull(r, row, col));
@@ -261,7 +261,7 @@ PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
return (r.getColumnLabel(column));
}
-std::string
+std::string
PgSqlExchange::dumpRow(const PgSqlResult& r, int row) {
r.rowCheck(row);
std::ostringstream stream;
@@ -269,7 +269,7 @@ PgSqlExchange::dumpRow(const PgSqlResult& r, int row) {
for (int col = 0; col < columns; ++col) {
const char* val = getRawColumnValue(r, row, col);
std::string name = r.getColumnLabel(col);
- int format = PQfformat(r, col);
+ int format = PQfformat(r, col);
stream << col << " " << name << " : " ;
if (format == PsqlBindArray::TEXT_FMT) {
diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc
index 12a3cc73f8..ef0703f5bc 100644
--- a/src/lib/dhcpsrv/pgsql_host_data_source.cc
+++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -44,7 +44,7 @@ const size_t OPTION_VALUE_MAX_LEN = 4096;
///
/// This value is used to validate whether the identifier type stored in
/// a database is within bounds. of supported identifiers.
-const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::IDENT_CIRCUIT_ID);
+const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
/// @brief Maximum length of DHCP identifier value.
const size_t DHCP_IDENTIFIER_MAX_LEN = 128;
@@ -91,7 +91,7 @@ public:
: PgSqlExchange(HOST_COLUMNS + additional_columns_num) {
// Set the column names for use by this class. This only comprises
// names used by the PgSqlHostExchange class. Derived classes will
- // need to set names for the columns they use. Currenty these are
+ // need to set names for the columns they use. Currently these are
// only used for logging purposes.
columns_[HOST_ID_COL] = "host_id";
columns_[DHCP_IDENTIFIER_COL] = "dhcp_identifier";
@@ -231,7 +231,7 @@ public:
/// adding duplicated hosts to the collection, assuming that processed
/// rows are primarily ordered by host id column.
///
- /// This method must be overriden in the derived classes to also
+ /// This method must be overridden in the derived classes to also
/// retrieve IPv6 reservations and DHCP options associated with a host.
///
/// @param [out] hosts Collection of hosts to which a new host created
@@ -392,7 +392,7 @@ private:
/// DHCPv6 options.
///
/// The following are the basic functions of this class:
- /// - bind class members to specific columns in MySQL binding tables,
+ /// - bind class members to specific columns in PgSQL binding tables,
/// - set DHCP options specific column names,
/// - create instances of options retrieved from the database.
///
@@ -418,7 +418,7 @@ private:
most_recent_option_id_(0) {
}
- /// @brief Reintializes state information
+ /// @brief Reinitializes state information
///
/// This function should be called prior to processing a fetched
/// set of options.
@@ -689,8 +689,8 @@ public:
///
/// The fetched row includes both host information and DHCP option
/// information. Because the SELECT queries use one or more LEFT JOIN
- /// clauses, the result set may contain duplicated host or options
- /// entries. This method detects duplicated information and discards such
+ /// clauses, the result set may contain duplicated host or options
+ /// entries. This method detects duplicated information and discards such
/// entries.
///
/// @param [out] hosts Container holding parsed hosts and options.
@@ -826,8 +826,6 @@ public:
/// @brief Creates IPv6 reservation from the data contained in the
/// currently processed row.
///
- /// Called after the MYSQL_BIND array created by createBindForReceive().
- ///
/// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) {
@@ -901,7 +899,7 @@ public:
" IPv6 reservation");
}
- // If we have reservation id we havent' seen yet, retrive the
+ // If we have reservation id we havent' seen yet, retrieve the
// the reservation, adding it to the current host
uint64_t reservation_id = getReservationId(r, row);
if (reservation_id && (reservation_id > most_recent_reservation_id_)) {
@@ -936,7 +934,7 @@ private:
};
-/// @brief This class is used for storing IPv6 reservations in a MySQL database.
+/// @brief This class is used for storing IPv6 reservations in a PgSQL database.
///
/// This class is only used to insert IPv6 reservations into the
/// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
@@ -1164,7 +1162,7 @@ public:
/// @brief Statement Tags
///
/// The contents of the enum are indexes into the list of SQL statements.
- /// It is assumed that the order is such that the indicies of statements
+ /// It is assumed that the order is such that the indices of statements
/// reading the database are less than those of statements modifying the
/// database.
enum StatementIndex {
@@ -1180,6 +1178,9 @@ public:
INSERT_V6_RESRV, // Insert v6 reservation
INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
+ DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
+ DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
+ DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
NUM_STATEMENTS // Number of statements
};
@@ -1202,8 +1203,7 @@ public:
/// @brief Executes statements which insert a row into one of the tables.
///
/// @param stindex Index of a statement being executed.
- /// @param bind Vector of MYSQL_BIND objects to be used when making the
- /// query.
+ /// @param bind Vector of PgsqlBindArray objects to be used for the query
/// @param return_last_id flag indicating whether or not the insert
/// returns the primary key of from the row inserted via " RETURNING
/// <primary key> as pid" clause on the INSERT statement. The RETURNING
@@ -1219,6 +1219,14 @@ public:
PsqlBindArrayPtr& bind,
const bool return_last_id = false);
+ /// @brief Executes statements that delete records.
+ ///
+ /// @param stindex Index of a statement being executed.
+ /// @param bind pointer to PsqlBindArray objects to be used for the query
+ /// @return true if any records were deleted, false otherwise
+ bool delStatement(PgSqlHostDataSourceImpl::StatementIndex stindex,
+ PsqlBindArrayPtr& bind);
+
/// @brief Inserts IPv6 Reservation into ipv6_reservation table.
///
/// @param resv IPv6 Reservation to be added
@@ -1244,7 +1252,8 @@ public:
/// @param stindex Index of a statement being executed.
/// @param options_cfg An object holding a collection of options to be
/// inserted into the database.
- /// @param host_id Host identifier retrieved using @c mysql_insert_id.
+ /// @param host_id Host identifier retrieved using getColumnValue
+ /// in addStatement method
void addOptions(const StatementIndex& stindex,
const ConstCfgOptionPtr& options_cfg,
const uint64_t host_id);
@@ -1260,7 +1269,7 @@ public:
/// @ref Host objects depends on the type of the exchange object.
///
/// @param stindex Statement index.
- /// @param bind Pointer to an array of MySQL bindings.
+ /// @param bind Pointer to an array of PgSQL bindings.
/// @param exchange Pointer to the exchange object used for the
/// particular query.
/// @param [out] result Reference to the collection of hosts returned.
@@ -1277,7 +1286,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
/// @param stindex Statement index.
@@ -1334,7 +1343,7 @@ public:
/// or dhcp6_options table.
boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
- /// @brief MySQL connection
+ /// @brief PgSQL connection
PgSqlConnection conn_;
/// @brief Indicates if the database is opened in read only mode.
@@ -1508,7 +1517,7 @@ TaggedStatementArray tagged_statements = { {
},
// PgSqlHostDataSourceImpl::GET_VERSION
- // Retrieves MySQL schema version.
+ // Retrieves PgSQL schema version.
{0,
{ OID_NONE },
"get_version",
@@ -1517,7 +1526,7 @@ TaggedStatementArray tagged_statements = { {
// PgSqlHostDataSourceImpl::INSERT_HOST
// Inserts a host into the 'hosts' table. Returns the inserted host id.
- {11,
+ {11,
{ OID_BYTEA, OID_INT2,
OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR,
OID_VARCHAR, OID_VARCHAR },
@@ -1561,6 +1570,34 @@ TaggedStatementArray tagged_statements = { {
"INSERT INTO dhcp6_options(code, value, formatted_value, space, "
" persistent, host_id, scope_id) "
"VALUES ($1, $2, $3, $4, $5, $6, 3)"
+ },
+
+ // PgSqlHostDataSourceImpl::DEL_HOST_ADDR4
+ // Deletes a v4 host that matches (subnet-id, addr4)
+ {2,
+ { OID_INT4, OID_INT8 },
+ "del_host_addr4",
+ "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 AND ipv4_address = $2"
+ },
+
+ // PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID
+ // Deletes a v4 host that matches (subnet4-id, identifier-type, identifier)
+ {3,
+ { OID_INT4, OID_INT2, OID_BYTEA },
+ "del_host_subid4_id",
+ "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 "
+ "AND dhcp_identifier_type = $2 "
+ "AND dhcp_identifier = $3"
+ },
+
+ // PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID
+ // Deletes a v6 host that matches (subnet6-id, identifier-type, identifier)
+ {3,
+ { OID_INT4, OID_INT2, OID_BYTEA },
+ "del_host_subid6_id",
+ "DELETE FROM hosts WHERE dhcp6_subnet_id = $1 "
+ "AND dhcp_identifier_type = $2 "
+ "AND dhcp_identifier = $3"
}
}
};
@@ -1634,6 +1671,33 @@ PgSqlHostDataSourceImpl::addStatement(StatementIndex stindex,
}
+bool
+PgSqlHostDataSourceImpl::delStatement(StatementIndex stindex,
+ PsqlBindArrayPtr& bind_array) {
+ PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
+ tagged_statements[stindex].nbparams,
+ &bind_array->values_[0],
+ &bind_array->lengths_[0],
+ &bind_array->formats_[0], 0));
+
+ int s = PQresultStatus(r);
+
+ if (s != PGRES_COMMAND_OK) {
+ // Connection determines if the error is fatal or not, and
+ // throws the appropriate exception
+ conn_.checkStatementError(r, tagged_statements[stindex]);
+ }
+
+ // Now check how many rows (hosts) were deleted. This should be either
+ // "0" or "1".
+ char* rows_deleted = PQcmdTuples(r);
+ if (!rows_deleted) {
+ isc_throw(DbOperationError,
+ "Could not retrieve the number of deleted rows.");
+ }
+ return (rows_deleted[0] != '0');
+}
+
void
PgSqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
const HostID& id) {
@@ -1787,7 +1851,7 @@ PgSqlHostDataSource::add(const HostPtr& host) {
// the PgSqlTransaction class.
PgSqlTransaction transaction(impl_->conn_);
- // Create the MYSQL_BIND array for the host
+ // Create the PgSQL Bind array for the host
PsqlBindArrayPtr bind_array = impl_->host_exchange_->createBindForSend(host);
// ... and insert the host.
@@ -1821,6 +1885,71 @@ PgSqlHostDataSource::add(const HostPtr& host) {
transaction.commit();
}
+bool
+PgSqlHostDataSource::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly();
+
+ if (addr.isV4()) {
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+ bind_array->add(subnet_id);
+ bind_array->add(addr);
+ return (impl_->delStatement(PgSqlHostDataSourceImpl::DEL_HOST_ADDR4,
+ bind_array));
+ }
+
+ ConstHostPtr host = get6(subnet_id, addr);
+ if (!host) {
+ return (false);
+ }
+
+ return del6(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0],
+ host->getIdentifier().size());
+}
+
+bool
+PgSqlHostDataSource::del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Subnet-id
+ bind_array->add(subnet_id);
+
+ // identifier-type
+ bind_array->add(static_cast<uint8_t>(identifier_type));
+
+ // identifier
+ bind_array->add(identifier_begin, identifier_len);
+
+ return (impl_->delStatement(PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
+ bind_array));
+
+}
+
+bool
+PgSqlHostDataSource::del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Subnet-id
+ bind_array->add(subnet_id);
+
+ // identifier-type
+ bind_array->add(static_cast<uint8_t>(identifier_type));
+
+ // identifier
+ bind_array->add(identifier_begin, identifier_len);
+
+ return (impl_->delStatement(PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
+ bind_array));
+
+}
+
ConstHostCollection
PgSqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
const DuidPtr& duid) const {
@@ -1880,11 +2009,11 @@ PgSqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
/// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid)
if (hwaddr && duid) {
- isc_throw(BadValue, "MySQL host data source get4() called with both"
+ isc_throw(BadValue, "PgSQL host data source get4() called with both"
" hwaddr and duid, only one of them is allowed");
}
if (!hwaddr && !duid) {
- isc_throw(BadValue, "MySQL host data source get4() called with "
+ isc_throw(BadValue, "PgSQL host data source get4() called with "
"neither hwaddr or duid specified, one of them is required");
}
@@ -1949,11 +2078,11 @@ PgSqlHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid,
/// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr, duid)
if (hwaddr && duid) {
- isc_throw(BadValue, "MySQL host data source get6() called with both"
+ isc_throw(BadValue, "PgSQL host data source get6() called with both"
" hwaddr and duid, only one of them is allowed");
}
if (!hwaddr && !duid) {
- isc_throw(BadValue, "MySQL host data source get6() called with "
+ isc_throw(BadValue, "PgSQL host data source get6() called with "
"neither hwaddr or duid specified, one of them is required");
}
diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h
index 846ac0f140..802d587cbf 100644
--- a/src/lib/dhcpsrv/pgsql_host_data_source.h
+++ b/src/lib/dhcpsrv/pgsql_host_data_source.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -96,7 +96,7 @@ public:
/// because a particular client may have reservations in multiple subnets.
///
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -119,7 +119,7 @@ public:
/// @brief Returns a host connected to the IPv4 subnet.
///
/// Implementations of this method should guard against the case when
- /// mutliple instances of the @c Host are present, e.g. when two
+ /// multiple instances of the @c Host are present, e.g. when two
/// @c Host objects are found, one for the DUID, another one for the
/// HW address. In such case, an implementation of this method
/// should throw an MultipleRecords exception.
@@ -138,7 +138,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -168,7 +168,7 @@ public:
/// @brief Returns a host connected to the IPv6 subnet.
///
/// Implementations of this method should guard against the case when
- /// mutliple instances of the @c Host are present, e.g. when two
+ /// multiple instances of the @c Host are present, e.g. when two
/// @c Host objects are found, one for the DUID, another one for the
/// HW address. In such case, an implementation of this method
/// should throw an MultipleRecords exception.
@@ -187,7 +187,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -221,7 +221,7 @@ public:
/// The method will insert the given host and all of its children (v4
/// options, v6 options, and v6 reservations) into the database. It
/// relies on constraints defined as part of the PostgreSQL schema to
- /// defend against duplicate entries and to ensure referential
+ /// defend against duplicate entries and to ensure referential
/// integrity.
///
/// Violation of any of these constraints for a host will result in a
@@ -232,7 +232,7 @@ public:
/// -# DHCP ID, DHCP ID TYPE, and DHCP4_SUBNET_ID combination must be unique
/// -# DHCP ID, DHCP ID TYPE, and DHCP6_SUBNET_ID combination must be unique
///
- /// In addition, violating the following referential contraints will
+ /// In addition, violating the following referential constraints will
/// a DbOperationError exception:
///
/// -# DHCP ID TYPE must be defined in the HOST_IDENTIFIER_TYPE table
@@ -251,6 +251,40 @@ public:
/// violation
virtual void add(const HostPtr& host);
+ /// @brief Attempts to delete a host by (subnet-id, address)
+ ///
+ /// This method supports both v4 and v6.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr);
+
+ /// @brief Attempts to delete a host by (subnet4-id, identifier type, identifier)
+ ///
+ /// This method supports v4 hosts only.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Attempts to delete a host by (subnet6-id, identifier type, identifier)
+ ///
+ /// This method supports v6 hosts only.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
/// @brief Return backend type
///
/// Returns the type of database as the string "postgresql". This is
diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc
index 9d64acdc96..c2d0d43f39 100644
--- a/src/lib/dhcpsrv/pgsql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -26,8 +26,8 @@ using namespace std;
namespace {
-/// @todo TKM lease6 needs to accomodate hwaddr,hwtype, and hwaddr source
-/// columns. This is coverd by tickets #3557, #4530, and PR#9.
+/// @todo TKM lease6 needs to accommodate hwaddr,hwtype, and hwaddr source
+/// columns. This is covered by tickets #3557, #4530, and PR#9.
/// @brief Catalog of all the SQL statements currently supported. Note
/// that the order columns appear in statement body must match the order they
@@ -230,7 +230,6 @@ namespace dhcp {
/// database.
class PgSqlLeaseExchange : public PgSqlExchange {
public:
-
PgSqlLeaseExchange()
: addr_str_(""), valid_lifetime_(0), valid_lft_str_(""),
expire_(0), expire_str_(""), subnet_id_(0), subnet_id_str_(""),
@@ -705,7 +704,7 @@ private:
/// @brief Base PgSql derivation of the statistical lease data query
///
-/// This class provides the functionality such as results storgae and row
+/// This class provides the functionality such as results storage and row
/// fetching common to fulfilling the statistical lease data query.
///
class PgSqlLeaseStatsQuery : public LeaseStatsQuery {
@@ -819,12 +818,14 @@ PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
<< " does not match expected count:" << NUM_STATEMENTS);
}
- pair<uint32_t, uint32_t> code_version(PG_CURRENT_VERSION, PG_CURRENT_MINOR);
+ pair<uint32_t, uint32_t> code_version(PG_SCHEMA_VERSION_MAJOR, PG_SCHEMA_VERSION_MINOR);
pair<uint32_t, uint32_t> db_version = getVersion();
if (code_version != db_version) {
- isc_throw(DbOpenError, "Posgresql schema version mismatch: need version: "
- << code_version.first << "." << code_version.second
- << " found version: " << db_version.first << "." << db_version.second);
+ isc_throw(DbOpenError,
+ "PostgreSQL schema version mismatch: need version: "
+ << code_version.first << "." << code_version.second
+ << " found version: " << db_version.first << "."
+ << db_version.second);
}
}
@@ -834,8 +835,8 @@ PgSqlLeaseMgr::~PgSqlLeaseMgr() {
std::string
PgSqlLeaseMgr::getDBVersion() {
std::stringstream tmp;
- tmp << "PostgreSQL backend " << PG_CURRENT_VERSION;
- tmp << "." << PG_CURRENT_MINOR;
+ tmp << "PostgreSQL backend " << PG_SCHEMA_VERSION_MAJOR;
+ tmp << "." << PG_SCHEMA_VERSION_MINOR;
tmp << ", library " << PQlibVersion();
return (tmp.str());
}
@@ -1362,6 +1363,16 @@ PgSqlLeaseMgr::startLeaseStatsQuery6() {
return(query);
}
+size_t
+PgSqlLeaseMgr::wipeLeases4(const SubnetID& /*subnet_id*/) {
+ isc_throw(NotImplemented, "wipeLeases4 is not implemented for PgSQL backend");
+}
+
+size_t
+PgSqlLeaseMgr::wipeLeases6(const SubnetID& /*subnet_id*/) {
+ isc_throw(NotImplemented, "wipeLeases6 is not implemented for PgSQL backend");
+}
+
string
PgSqlLeaseMgr::getName() const {
string name = "";
diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.h b/src/lib/dhcpsrv/pgsql_lease_mgr.h
index 69924067a3..01b9ae41a9 100644
--- a/src/lib/dhcpsrv/pgsql_lease_mgr.h
+++ b/src/lib/dhcpsrv/pgsql_lease_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -25,10 +25,6 @@ namespace dhcp {
class PgSqlLease4Exchange;
class PgSqlLease6Exchange;
-/// Defines PostgreSQL backend version: 3.0
-const uint32_t PG_CURRENT_VERSION = 3;
-const uint32_t PG_CURRENT_MINOR = 0;
-
/// @brief PostgreSQL Lease Manager
///
/// This class provides the \ref isc::dhcp::LeaseMgr interface to the PostgreSQL
@@ -323,7 +319,7 @@ public:
/// invokes its start method, which fetches its statistical data
/// result set by executing the RECOUNT_LEASE_STATS4 query.
/// The query object is then returned.
- ///
+ ///
/// @return The populated query as a pointer to an LeaseStatsQuery
virtual LeaseStatsQueryPtr startLeaseStatsQuery4();
@@ -333,10 +329,32 @@ public:
/// invokes its start method, which fetches its statistical data
/// result set by executing the RECOUNT_LEASE_STATS6 query.
/// The query object is then returned.
- ///
+ ///
/// @return The populated query as a pointer to an LeaseStatsQuery
virtual LeaseStatsQueryPtr startLeaseStatsQuery6();
+ /// @brief Removes specified IPv4 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @todo: Not implemented yet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases4(const SubnetID& subnet_id);
+
+ /// @brief Removed specified IPv6 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @todo: Not implemented yet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases6(const SubnetID& subnet_id);
+
/// @brief Return backend type
///
/// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
@@ -499,7 +517,7 @@ private:
///
/// This method performs the common actions for the various getLease4()
/// methods. It acts as an interface to the getLeaseCollection() method,
- /// but retrieveing only a single lease.
+ /// but retrieving only a single lease.
///
/// @param stindex Index of statement being executed
/// @param bind_array array containing input parameters for the query
@@ -511,7 +529,7 @@ private:
///
/// This method performs the common actions for the various getLease4()
/// methods. It acts as an interface to the getLeaseCollection() method,
- /// but retrieveing only a single lease.
+ /// but retrieving only a single lease.
///
/// @param stindex Index of statement being executed
/// @param bind_array array containing input parameters for the query
diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc
index 15112b0147..77cdc46121 100644
--- a/src/lib/dhcpsrv/pool.cc
+++ b/src/lib/dhcpsrv/pool.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
#include <sstream>
using namespace isc::asiolink;
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -74,6 +75,46 @@ Pool4::Pool4( const isc::asiolink::IOAddress& prefix, uint8_t prefix_len)
capacity_ = addrsInRange(prefix, last_);
}
+data::ElementPtr
+Pool::toElement() const {
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+
+ // Set user-context
+ ConstElementPtr context = getContext();
+ if (!isNull(context)) {
+ map->set("user-context", context);
+ }
+
+ // Set pool options
+ ConstCfgOptionPtr opts = getCfgOption();
+ map->set("option-data", opts->toElement());
+ return (map);
+}
+
+data::ElementPtr
+Pool4::toElement() const {
+ // Prepare the map
+ ElementPtr map = Pool::toElement();
+
+ // Set pool
+ const IOAddress& first = getFirstAddress();
+ const IOAddress& last = getLastAddress();
+ std::string range = first.toText() + "-" + last.toText();
+
+ // Try to output a prefix (vs a range)
+ int prefix_len = prefixLengthFromRange(first, last);
+ if (prefix_len >= 0) {
+ std::ostringstream oss;
+ oss << first.toText() << "/" << prefix_len;
+ range = oss.str();
+ }
+
+ map->set("pool", Element::create(range));
+ return (map);
+}
+
+
Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last)
: Pool(type, first, last), prefix_len_(128), pd_exclude_option_() {
@@ -170,7 +211,7 @@ Pool6::Pool6(const asiolink::IOAddress& prefix, const uint8_t prefix_len,
}
/// @todo Check that the prefixes actually match. Theoretically, a
- /// user could specify a prefix which sets insgnificant bits. We should
+ /// user could specify a prefix which sets insignificant bits. We should
/// clear insignificant bits based on the prefix length but this
/// should be considered a part of the IOAddress class, perhaps and
/// requires a bit of work (mainly in terms of testing).
@@ -235,6 +276,72 @@ Pool6::init(const Lease::Type& type,
}
}
+data::ElementPtr
+Pool6::toElement() const {
+ // Prepare the map
+ ElementPtr map = Pool::toElement();
+
+ switch (getType()) {
+ case Lease::TYPE_NA: {
+ const IOAddress& first = getFirstAddress();
+ const IOAddress& last = getLastAddress();
+ std::string range = first.toText() + "-" + last.toText();
+
+ // Try to output a prefix (vs a range)
+ int prefix_len = prefixLengthFromRange(first, last);
+ if (prefix_len >= 0) {
+ std::ostringstream oss;
+ oss << first.toText() << "/" << prefix_len;
+ range = oss.str();
+ }
+
+ map->set("pool", Element::create(range));
+ break;
+ }
+ case Lease::TYPE_PD: {
+ // Set prefix
+ const IOAddress& prefix = getFirstAddress();
+ map->set("prefix", Element::create(prefix.toText()));
+
+ // Set prefix-len (get it from min - max)
+ const IOAddress& last = getLastAddress();
+ int prefix_len = prefixLengthFromRange(prefix, last);
+ if (prefix_len < 0) {
+ // The pool is bad: give up
+ isc_throw(ToElementError, "invalid prefix range "
+ << prefix.toText() << "-" << last.toText());
+ }
+
+ // Set delegated-len
+ uint8_t len = getLength();
+ map->set("delegated-len", Element::create(static_cast<int>(len)));
+
+ // Set excluded prefix
+ const Option6PDExcludePtr& xopt = getPrefixExcludeOption();
+ if (xopt) {
+ const IOAddress& xprefix = xopt->getExcludedPrefix(prefix, len);
+ map->set("excluded-prefix", Element::create(xprefix.toText()));
+
+ uint8_t xlen = xopt->getExcludedPrefixLength();
+ map->set("excluded-prefix-len",
+ Element::create(static_cast<int>(xlen)));
+ } else {
+ map->set("excluded-prefix", Element::create(std::string("::")));
+ map->set("excluded-prefix-len", Element::create(0));
+ }
+
+ break;
+ }
+ default:
+ isc_throw(ToElementError, "Lease type: " << getType()
+ << ", unsupported for Pool6");
+ break;
+ }
+
+ return (map);
+}
+
+
std::string
Pool6::toText() const {
std::ostringstream s;
diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h
index cbeb71ae96..9f66370f91 100644
--- a/src/lib/dhcpsrv/pool.h
+++ b/src/lib/dhcpsrv/pool.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -106,6 +106,11 @@ public:
user_context_ = ctx;
}
+ /// @brief Unparse a pool object.
+ ///
+ /// @return A pointer to unparsed pool configuration.
+ virtual data::ElementPtr toElement() const;
+
protected:
/// @brief protected constructor
@@ -182,6 +187,11 @@ public:
/// @param prefix_len specifies length of the prefix of the pool
Pool4(const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len);
+
+ /// @brief Unparse a Pool4 object.
+ ///
+ /// @return A pointer to unparsed Pool4 configuration.
+ virtual data::ElementPtr toElement() const;
};
/// @brief a pointer an IPv4 Pool
@@ -263,7 +273,7 @@ public:
/// This may be useful for "prefix/len" style definition for
/// addresses, but is mostly useful for prefix pools.
/// @return prefix length (1-128)
- uint8_t getLength() {
+ uint8_t getLength() const {
return (prefix_len_);
}
@@ -275,6 +285,11 @@ public:
return (pd_exclude_option_);
}
+ /// @brief Unparse a Pool6 object.
+ ///
+ /// @return A pointer to unparsed Pool6 configuration.
+ virtual data::ElementPtr toElement() const;
+
/// @brief returns textual representation of the pool
///
/// @return textual representation
diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc
index 7c7830966f..ca71903d92 100644
--- a/src/lib/dhcpsrv/srv_config.cc
+++ b/src/lib/dhcpsrv/srv_config.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/srv_config.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/cfg_hosts_util.h>
#include <log/logger_manager.h>
#include <log/logger_specification.h>
#include <dhcp/pkt.h> // Needed for HWADDR_SOURCE_*
@@ -15,6 +16,7 @@
#include <sstream>
using namespace isc::log;
+using namespace isc::data;
namespace isc {
namespace dhcp {
@@ -29,7 +31,8 @@ SrvConfig::SrvConfig()
cfg_host_operations4_(CfgHostOperations::createConfig4()),
cfg_host_operations6_(CfgHostOperations::createConfig6()),
class_dictionary_(new ClientClassDictionary()),
- decline_timer_(0), dhcp4o6_port_(0) {
+ decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0),
+ d2_client_config_(new D2ClientConfig()) {
}
SrvConfig::SrvConfig(const uint32_t sequence)
@@ -42,7 +45,8 @@ SrvConfig::SrvConfig(const uint32_t sequence)
cfg_host_operations4_(CfgHostOperations::createConfig4()),
cfg_host_operations6_(CfgHostOperations::createConfig6()),
class_dictionary_(new ClientClassDictionary()),
- decline_timer_(0), dhcp4o6_port_(0) {
+ decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0),
+ d2_client_config_(new D2ClientConfig()) {
}
std::string
@@ -70,7 +74,7 @@ SrvConfig::getConfigSummary(const uint32_t selection) const {
}
if ((selection & CFGSEL_DDNS) == CFGSEL_DDNS) {
- bool ddns_enabled = CfgMgr::instance().ddnsEnabled();
+ bool ddns_enabled = getD2ClientConfig()->getEnableUpdates();
s << "DDNS: " << (ddns_enabled ? "enabled" : "disabled") << "; ";
}
@@ -106,7 +110,16 @@ SrvConfig::copy(SrvConfig& new_config) const {
cfg_option_->copyTo(*new_config.cfg_option_);
// Replace the client class dictionary
new_config.class_dictionary_.reset(new ClientClassDictionary(*class_dictionary_));
-
+ // Replace the D2 client configuration
+ new_config.setD2ClientConfig(getD2ClientConfig());
+ // Replace configured hooks libraries.
+ new_config.hooks_config_.clear();
+ using namespace isc::hooks;
+ for (HookLibsCollection::const_iterator it =
+ hooks_config_.get().begin();
+ it != hooks_config_.get().end(); ++it) {
+ new_config.hooks_config_.add(it->first, it->second);
+ }
}
void
@@ -148,10 +161,21 @@ SrvConfig::equals(const SrvConfig& other) const {
}
}
// Logging information is equal between objects, so check other values.
- return ((*cfg_iface_ == *other.cfg_iface_) &&
- (*cfg_option_def_ == *other.cfg_option_def_) &&
- (*cfg_option_ == *other.cfg_option_) &&
- (*class_dictionary_ == *other.class_dictionary_));
+ if ((*cfg_iface_ != *other.cfg_iface_) ||
+ (*cfg_option_def_ != *other.cfg_option_def_) ||
+ (*cfg_option_ != *other.cfg_option_) ||
+ (*class_dictionary_ != *other.class_dictionary_) ||
+ (*d2_client_config_ != *other.d2_client_config_)) {
+ return (false);
+ }
+ // Now only configured hooks libraries can differ.
+ // If number of configured hooks libraries are different, then
+ // configurations aren't equal.
+ if (hooks_config_.get().size() != other.hooks_config_.get().size()) {
+ return (false);
+ }
+ // Pass through all configured hooks libraries.
+ return (hooks_config_.equal(other.hooks_config_));
}
void
@@ -178,5 +202,119 @@ SrvConfig::updateStatistics() {
}
}
+ElementPtr
+SrvConfig::toElement() const {
+ // Get family for the configuration manager
+ uint16_t family = CfgMgr::instance().getFamily();
+ // Toplevel map
+ ElementPtr result = Element::createMap();
+ // DhcpX global map
+ ElementPtr dhcp = Element::createMap();
+ // Set decline-probation-period
+ dhcp->set("decline-probation-period",
+ Element::create(static_cast<long long>(decline_timer_)));
+ // Set echo-client-id (DHCPv4)
+ if (family == AF_INET) {
+ dhcp->set("echo-client-id", Element::create(echo_v4_client_id_));
+ }
+ // Set dhcp4o6-port
+ dhcp->set("dhcp4o6-port",
+ Element::create(static_cast<int>(dhcp4o6_port_)));
+ // Set dhcp-ddns
+ dhcp->set("dhcp-ddns", d2_client_config_->toElement());
+ // Set interfaces-config
+ dhcp->set("interfaces-config", cfg_iface_->toElement());
+ // Set option-def
+ dhcp->set("option-def", cfg_option_def_->toElement());
+ // Set option-data
+ dhcp->set("option-data", cfg_option_->toElement());
+ // Set subnets
+ ConstElementPtr subnets;
+ if (family == AF_INET) {
+ subnets = cfg_subnets4_->toElement();
+ dhcp->set("subnet4", subnets);
+ } else {
+ subnets = cfg_subnets6_->toElement();
+ dhcp->set("subnet6", subnets);
+ }
+ // Insert reservations
+ CfgHostsList resv_list;
+ resv_list.internalize(cfg_hosts_->toElement());
+ const std::vector<ElementPtr>& sn_list = subnets->listValue();
+ for (std::vector<ElementPtr>::const_iterator subnet = sn_list.begin();
+ subnet != sn_list.end(); ++subnet) {
+ ConstElementPtr id = (*subnet)->get("id");
+ if (isNull(id)) {
+ isc_throw(ToElementError, "subnet has no id");
+ }
+ SubnetID subnet_id = id->intValue();
+ ConstElementPtr resvs = resv_list.get(subnet_id);
+ (*subnet)->set("reservations", resvs);
+ }
+ // Set expired-leases-processing
+ ConstElementPtr expired = cfg_expiration_->toElement();
+ dhcp->set("expired-leases-processing", expired);
+ if (family == AF_INET6) {
+ // Set server-id (DHCPv6)
+ dhcp->set("server-id", cfg_duid_->toElement());
+
+ // Set relay-supplied-options (DHCPv6)
+ dhcp->set("relay-supplied-options", cfg_rsoo_->toElement());
+ }
+ // Set lease-database
+ CfgLeaseDbAccess lease_db(*cfg_db_access_);
+ dhcp->set("lease-database", lease_db.toElement());
+ // Set hosts-database
+ CfgHostDbAccess host_db(*cfg_db_access_);
+ // @todo accept empty map
+ ConstElementPtr hosts_database = host_db.toElement();
+ if (hosts_database->size() > 0) {
+ dhcp->set("hosts-database", hosts_database);
+ }
+ // Set host-reservation-identifiers
+ ConstElementPtr host_ids;
+ if (family == AF_INET) {
+ host_ids = cfg_host_operations4_->toElement();
+ } else {
+ host_ids = cfg_host_operations6_->toElement();
+ }
+ dhcp->set("host-reservation-identifiers", host_ids);
+ // Set mac-sources (DHCPv6)
+ if (family == AF_INET6) {
+ dhcp->set("mac-sources", cfg_mac_source_.toElement());
+ }
+ // Set control-socket (skip if null as empty is not legal)
+ if (!isNull(control_socket_)) {
+ dhcp->set("control-socket", control_socket_);
+ }
+ // Set client-classes
+ ConstElementPtr client_classes = class_dictionary_->toElement();
+ // @todo accept empty list
+ if (!client_classes->empty()) {
+ dhcp->set("client-classes", client_classes);
+ }
+ // Set hooks-libraries
+ ConstElementPtr hooks_libs = hooks_config_.toElement();
+ dhcp->set("hooks-libraries", hooks_libs);
+ // Set DhcpX
+ result->set(family == AF_INET ? "Dhcp4" : "Dhcp6", dhcp);
+
+ // Logging global map (skip if empty)
+ if (!logging_info_.empty()) {
+ ElementPtr logging = Element::createMap();
+ // Set loggers list
+ ElementPtr loggers = Element::createList();
+ for (LoggingInfoStorage::const_iterator logger =
+ logging_info_.cbegin();
+ logger != logging_info_.cend(); ++logger) {
+ loggers->add(logger->toElement());
+ }
+ logging->set("loggers", loggers);
+ result->set("Logging", logging);
+ }
+
+ return (result);
+}
+
}
}
diff --git a/src/lib/dhcpsrv/srv_config.h b/src/lib/dhcpsrv/srv_config.h
index 5ab02d1b15..1dcf76a745 100644
--- a/src/lib/dhcpsrv/srv_config.h
+++ b/src/lib/dhcpsrv/srv_config.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
#ifndef DHCPSRV_CONFIG_H
#define DHCPSRV_CONFIG_H
+#include <cc/cfg_to_element.h>
#include <dhcpsrv/cfg_db_access.h>
#include <dhcpsrv/cfg_duid.h>
#include <dhcpsrv/cfg_expiration.h>
@@ -20,7 +21,9 @@
#include <dhcpsrv/cfg_subnets6.h>
#include <dhcpsrv/cfg_mac_source.h>
#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/d2_client_cfg.h>
#include <dhcpsrv/logging_info.h>
+#include <hooks/hooks_config.h>
#include <cc/data.h>
#include <boost/shared_ptr.hpp>
#include <vector>
@@ -35,7 +38,7 @@ class CfgMgr;
/// @brief Specifies current DHCP configuration
///
/// @todo Migrate all other configuration parameters from cfgmgr.h here
-class SrvConfig {
+class SrvConfig : public isc::data::CfgToElement {
public:
/// @name Constants for selection of parameters returned by @c getConfigSummary
///
@@ -364,7 +367,21 @@ public:
class_dictionary_ = dictionary;
}
- /// @brief Copies the currnet configuration to a new configuration.
+ /// @brief Returns non-const reference to configured hooks libraries.
+ ///
+ /// @return non-const reference to configured hooks libraries.
+ isc::hooks::HooksConfig& getHooksConfig() {
+ return (hooks_config_);
+ }
+
+ /// @brief Returns const reference to configured hooks libraries.
+ ///
+ /// @return const reference to configured hooks libraries.
+ const isc::hooks::HooksConfig& getHooksConfig() const {
+ return (hooks_config_);
+ }
+
+ /// @brief Copies the current configuration to a new configuration.
///
/// This method copies the parameters stored in the configuration to
/// an object passed as parameter. The configuration sequence is not
@@ -376,7 +393,7 @@ public:
/// the default copy constructors can't be used. Implementing this
/// requires quite a lot of time so this is left as is for now.
/// The lack of ability to copy the entire configuration makes
- /// revert function of the @c CfgMgr unsuable.
+ /// revert function of the @c CfgMgr unusable.
///
/// @param [out] new_config An object to which the configuration will
/// be copied.
@@ -469,24 +486,61 @@ public:
return (decline_timer_);
}
+ /// @brief Sets whether server should send back client-id in DHCPv4
+ ///
+ /// This is a compatibility flag. The default (true) is compliant with
+ /// RFC6842. False is for backward compatibility.
+ ///
+ /// @param echo should the client-id be sent or not
+ void setEchoClientId(const bool echo) {
+ echo_v4_client_id_ = echo;
+ }
+
+ /// @brief Returns whether server should send back client-id in DHCPv4.
+ /// @return true if client-id should be returned, false otherwise.
+ bool getEchoClientId() const {
+ return (echo_v4_client_id_);
+ }
+
/// @brief Sets DHCP4o6 IPC port
///
/// DHCPv4-over-DHCPv6 uses a UDP socket for interserver communication,
/// this socket is bound and connected to this port and port + 1
///
/// @param port port and port + 1 to use
- void setDhcp4o6Port(uint32_t port) {
+ void setDhcp4o6Port(uint16_t port) {
dhcp4o6_port_ = port;
}
/// @brief Returns DHCP4o6 IPC port
///
- /// See @ref setDhcp4o6Port or brief discussion.
+ /// See @ref setDhcp4o6Port for brief discussion.
/// @return value of DHCP4o6 IPC port
- uint32_t getDhcp4o6Port() {
+ uint16_t getDhcp4o6Port() {
return (dhcp4o6_port_);
}
+ /// @brief Returns pointer to the D2 client configuration
+ D2ClientConfigPtr getD2ClientConfig() {
+ return (d2_client_config_);
+ }
+
+ /// @brief Returns pointer to const D2 client configuration
+ const D2ClientConfigPtr getD2ClientConfig() const {
+ return (d2_client_config_);
+ }
+
+ /// @brief Sets the D2 client configuration
+ /// @param d2_client_config pointer to the new D2 client configuration
+ void setD2ClientConfig(const D2ClientConfigPtr& d2_client_config) {
+ d2_client_config_ = d2_client_config;
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
private:
/// @brief Sequence number identifying the configuration.
@@ -559,17 +613,25 @@ private:
/// @brief Pointer to the dictionary of global client class definitions
ClientClassDictionaryPtr class_dictionary_;
+ /// @brief Configured hooks libraries.
+ isc::hooks::HooksConfig hooks_config_;
+
/// @brief Decline Period time
///
/// This timer specifies decline probation period, the time after a declined
/// lease is recovered back to available state. Expressed in seconds.
uint32_t decline_timer_;
+ /// @brief Indicates whether v4 server should send back client-id
+ bool echo_v4_client_id_;
+
/// @brief DHCP4o6 IPC port
///
/// DHCPv4-over-DHCPv6 uses a UDP socket for interserver communication,
/// this socket is bound and connected to this port and port + 1
- uint32_t dhcp4o6_port_;
+ uint16_t dhcp4o6_port_;
+
+ D2ClientConfigPtr d2_client_config_;
};
/// @name Pointers to the @c SrvConfig object.
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index b9cec1ef12..a28c0a9cbf 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -14,6 +14,7 @@
#include <sstream>
using namespace isc::asiolink;
+using namespace isc::data;
using namespace isc::dhcp;
namespace {
@@ -367,7 +368,7 @@ bool
Subnet::inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const {
// Let's start with checking if it even belongs to that subnet.
- if (!inRange(addr)) {
+ if ((type != Lease::TYPE_PD) && !inRange(addr)) {
return (false);
}
@@ -472,5 +473,220 @@ void Subnet6::checkType(Lease::Type type) const {
}
}
+data::ElementPtr
+Subnet::toElement() const {
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+
+ // Set subnet id
+ SubnetID id = getID();
+ map->set("id", Element::create(static_cast<long long>(id)));
+
+ // Set relay info
+ const Subnet::RelayInfo& relay_info = getRelayInfo();
+ ElementPtr relay = Element::createMap();
+ relay->set("ip-address", Element::create(relay_info.addr_.toText()));
+ map->set("relay", relay);
+
+ // Set subnet
+ map->set("subnet", Element::create(toText()));
+
+ // Set interface
+ const std::string& iface = getIface();
+ if (!iface.empty()) {
+ map->set("interface", Element::create(iface));
+ }
+
+ // Set renew-timer
+ map->set("renew-timer",
+ Element::create(static_cast<long long>
+ (getT1().get())));
+ // Set rebind-timer
+ map->set("rebind-timer",
+ Element::create(static_cast<long long>
+ (getT2().get())));
+ // Set valid-lifetime
+ map->set("valid-lifetime",
+ Element::create(static_cast<long long>
+ (getValid().get())));
+
+ // Set reservation mode
+ Subnet::HRMode hrmode = getHostReservationMode();
+ std::string mode;
+ switch (hrmode) {
+ case Subnet::HR_DISABLED:
+ mode = "disabled";
+ break;
+ case Subnet::HR_OUT_OF_POOL:
+ mode = "out-of-pool";
+ break;
+ case Subnet::HR_ALL:
+ mode = "all";
+ break;
+ default:
+ isc_throw(ToElementError,
+ "invalid host reservation mode: " << hrmode);
+ }
+ map->set("reservation-mode", Element::create(mode));
+
+ // Set client-class
+ const ClientClasses& cclasses = getClientClasses();
+ if (cclasses.size() > 1) {
+ isc_throw(ToElementError, "client-class has too many items: "
+ << cclasses.size());
+ } else if (!cclasses.empty()) {
+ map->set("client-class", Element::create(*cclasses.cbegin()));
+ }
+
+ // Set options
+ ConstCfgOptionPtr opts = getCfgOption();
+ map->set("option-data", opts->toElement());
+
+ return (map);
+}
+
+data::ElementPtr
+Subnet4::toElement() const {
+ // Prepare the map
+ ElementPtr map = Subnet::toElement();
+
+ // Set match-client-id
+ map->set("match-client-id", Element::create(getMatchClientId()));
+
+ // Set DHCP4o6
+ const Cfg4o6& d4o6 = get4o6();
+ isc::data::merge(map, d4o6.toElement());
+
+ // Set next-server
+ map->set("next-server", Element::create(getSiaddr().toText()));
+
+ // Set pools
+ const PoolCollection& pools = getPools(Lease::TYPE_V4);
+ ElementPtr pool_list = Element::createList();
+ for (PoolCollection::const_iterator pool = pools.cbegin();
+ pool != pools.cend(); ++pool) {
+ // Add the elementized pool to the list
+ pool_list->add((*pool)->toElement());
+ }
+ map->set("pools", pool_list);
+
+ return (map);
+}
+
+data::ElementPtr
+Subnet6::toElement() const {
+ // Prepare the map
+ ElementPtr map = Subnet::toElement();
+
+ // Set interface-id
+ const OptionPtr& ifaceid = getInterfaceId();
+ if (ifaceid) {
+ std::vector<uint8_t> bin = ifaceid->getData();
+ std::string ifid;
+ ifid.resize(bin.size());
+ if (!bin.empty()) {
+ std::memcpy(&ifid[0], &bin[0], bin.size());
+ }
+ map->set("interface-id", Element::create(ifid));
+ }
+
+ // Set preferred-lifetime
+ map->set("preferred-lifetime",
+ Element::create(static_cast<long long>
+ (getPreferred().get())));
+ // Set rapid-commit
+ bool rapid_commit = getRapidCommit();
+ map->set("rapid-commit", Element::create(rapid_commit));
+
+ // Set pools
+ const PoolCollection& pools = getPools(Lease::TYPE_NA);
+ ElementPtr pool_list = Element::createList();
+ for (PoolCollection::const_iterator pool = pools.cbegin();
+ pool != pools.cend(); ++pool) {
+ // Prepare the map for a pool (@todo move this code to pool.cc)
+ ElementPtr pool_map = Element::createMap();
+ // Set pool
+ const IOAddress& first = (*pool)->getFirstAddress();
+ const IOAddress& last = (*pool)->getLastAddress();
+ std::string range = first.toText() + "-" + last.toText();
+ // Try to output a prefix (vs a range)
+ int prefix_len = prefixLengthFromRange(first, last);
+ if (prefix_len >= 0) {
+ std::ostringstream oss;
+ oss << first.toText() << "/" << prefix_len;
+ range = oss.str();
+ }
+ pool_map->set("pool", Element::create(range));
+ // Set user-context
+ ConstElementPtr context = (*pool)->getContext();
+ if (!isNull(context)) {
+ pool_map->set("user-context", context);
+ }
+ // Set pool options
+ ConstCfgOptionPtr opts = (*pool)->getCfgOption();
+ pool_map->set("option-data", opts->toElement());
+ // Push on the pool list
+ pool_list->add(pool_map);
+ }
+ map->set("pools", pool_list);
+ // Set pd-pools
+ const PoolCollection& pdpools = getPools(Lease::TYPE_PD);
+ ElementPtr pdpool_list = Element::createList();
+ for (PoolCollection::const_iterator pool = pdpools.cbegin();
+ pool != pdpools.cend(); ++pool) {
+ // Get it as a Pool6 (@todo move this code to pool.cc)
+ const Pool6* pdpool = dynamic_cast<Pool6*>(pool->get());
+ if (!pdpool) {
+ isc_throw(ToElementError, "invalid pd-pool pointer");
+ }
+ // Prepare the map for a pd-pool
+ ElementPtr pool_map = Element::createMap();
+ // Set prefix
+ const IOAddress& prefix = pdpool->getFirstAddress();
+ pool_map->set("prefix", Element::create(prefix.toText()));
+ // Set prefix-len (get it from min - max)
+ const IOAddress& last = pdpool->getLastAddress();
+ int prefix_len = prefixLengthFromRange(prefix, last);
+ if (prefix_len < 0) {
+ // The pool is bad: give up
+ isc_throw(ToElementError, "invalid prefix range "
+ << prefix.toText() << "-" << last.toText());
+ }
+ pool_map->set("prefix-len", Element::create(prefix_len));
+ // Set delegated-len
+ uint8_t len = pdpool->getLength();
+ pool_map->set("delegated-len",
+ Element::create(static_cast<int>(len)));
+
+ // Set excluded prefix
+ const Option6PDExcludePtr& xopt =
+ pdpool->getPrefixExcludeOption();
+ if (xopt) {
+ const IOAddress& xprefix =
+ xopt->getExcludedPrefix(prefix, len);
+ pool_map->set("excluded-prefix",
+ Element::create(xprefix.toText()));
+ uint8_t xlen = xopt->getExcludedPrefixLength();
+ pool_map->set("excluded-prefix-len",
+ Element::create(static_cast<int>(xlen)));
+ }
+
+ // Set user-context
+ ConstElementPtr context = pdpool->getContext();
+ if (!isNull(context)) {
+ pool_map->set("user-context", context);
+ }
+ // Set pool options
+ ConstCfgOptionPtr opts = pdpool->getCfgOption();
+ pool_map->set("option-data", opts->toElement());
+ // Push on the pool list
+ pdpool_list->add(pool_map);
+ }
+ map->set("pd-pools", pdpool_list);
+
+ return (map);
+}
+
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index f4c083bf75..e11187bfa3 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#define SUBNET_H
#include <asiolink/io_address.h>
+#include <cc/data.h>
#include <dhcp/option.h>
#include <dhcp/classify.h>
#include <dhcp/option_space_container.h>
@@ -18,6 +19,11 @@
#include <dhcpsrv/subnet_id.h>
#include <dhcpsrv/triplet.h>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/random_access_index.hpp>
+#include <boost/multi_index_container.hpp>
#include <boost/shared_ptr.hpp>
namespace isc {
@@ -72,11 +78,12 @@ public:
/// @brief checks if the specified address is in pools
///
- /// Note the difference between inRange() and inPool(). For a given
- /// subnet (e.g. 2001::/64) there may be one or more pools defined
- /// that may or may not cover entire subnet, e.g. pool 2001::1-2001::10).
- /// inPool() returning true implies inRange(), but the reverse implication
- /// is not always true. For the given example, 2001::1234:abcd would return
+ /// Note the difference between inRange() and inPool() for addresses
+ /// (i.e. *not* prefixes). For a given subnet (e.g. 2001::/64) there
+ /// may be one or more pools defined that may or may not cover
+ /// entire subnet, e.g. pool 2001::1-2001::10). inPool() returning
+ /// true implies inRange(), but the reverse implication is not
+ /// always true. For the given example, 2001::1234:abcd would return
/// true for inRange(), but false for inPool() check.
///
/// @param type type of pools to iterate over
@@ -279,7 +286,7 @@ public:
/// returned it is valid.
///
/// @return const reference to the relay information
- const isc::dhcp::Subnet::RelayInfo& getRelayInfo() {
+ const isc::dhcp::Subnet::RelayInfo& getRelayInfo() const {
return (relay_);
}
@@ -308,6 +315,16 @@ public:
/// @param class_name client class to be supported by this subnet
void allowClientClass(const isc::dhcp::ClientClass& class_name);
+ /// @brief returns the client class white list
+ ///
+ /// @note The returned reference is only valid as long as the object
+ /// returned it is valid.
+ ///
+ /// @return client classes @ref white_list_
+ const isc::dhcp::ClientClasses& getClientClasses() const {
+ return (white_list_);
+ }
+
/// @brief Specifies what type of Host Reservations are supported.
///
/// Host reservations may be either in-pool (they reserve an address that
@@ -354,7 +371,7 @@ protected:
/// @param t2 T2 (rebind-time) timer, expressed in seconds
/// @param valid_lifetime valid lifetime of leases in this subnet (in seconds)
/// @param relay optional relay information (currently with address only)
- /// @param id arbitraty subnet id, value of 0 triggers autogeneration
+ /// @param id arbitrary subnet id, value of 0 triggers autogeneration
/// of subnet id
Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
const Triplet<uint32_t>& t1,
@@ -371,7 +388,7 @@ protected:
/// @brief keeps the subnet-id value
///
- /// It is inreased every time a new Subnet object is created. It is reset
+ /// It is incremented every time a new Subnet object is created. It is reset
/// (@ref resetSubnetID) every time reconfiguration
/// occurs.
///
@@ -415,6 +432,11 @@ protected:
/// type.
bool poolOverlaps(const Lease::Type& pool_type, const PoolPtr& pool) const;
+ /// @brief Unparse a subnet object.
+ ///
+ /// @return A pointer to unparsed subnet configuration.
+ virtual data::ElementPtr toElement() const;
+
/// @brief subnet-id
///
/// Subnet-id is a unique value that can be used to find or identify
@@ -436,13 +458,13 @@ protected:
/// @brief a prefix length of the subnet
uint8_t prefix_len_;
- /// @brief a tripet (min/default/max) holding allowed renew timer values
+ /// @brief a triplet (min/default/max) holding allowed renew timer values
Triplet<uint32_t> t1_;
- /// @brief a tripet (min/default/max) holding allowed rebind timer values
+ /// @brief a triplet (min/default/max) holding allowed rebind timer values
Triplet<uint32_t> t2_;
- /// @brief a tripet (min/default/max) holding allowed valid lifetime values
+ /// @brief a triplet (min/default/max) holding allowed valid lifetime values
Triplet<uint32_t> valid_;
/// @brief last allocated address
@@ -518,7 +540,7 @@ public:
/// @param t1 renewal timer (in seconds)
/// @param t2 rebind timer (in seconds)
/// @param valid_lifetime preferred lifetime of leases (in seconds)
- /// @param id arbitraty subnet id, default value of 0 triggers
+ /// @param id arbitrary subnet id, default value of 0 triggers
/// autogeneration of subnet id
Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
@@ -563,6 +585,19 @@ public:
return (dhcp4o6_);
}
+ /// @brief Returns const DHCP4o6 configuration parameters.
+ ///
+ /// This structure is always available. If the 4o6 is not enabled, its
+ /// enabled_ field will be set to false.
+ const Cfg4o6& get4o6() const {
+ return (dhcp4o6_);
+ }
+
+ /// @brief Unparse a subnet object.
+ ///
+ /// @return A pointer to unparsed subnet configuration.
+ virtual data::ElementPtr toElement() const;
+
private:
/// @brief Returns default address for pool selection
@@ -590,16 +625,12 @@ private:
Cfg4o6 dhcp4o6_;
};
-/// @brief A pointer to a @c Subnet4 object
+/// @brief A const pointer to a @c Subnet4 object.
+typedef boost::shared_ptr<const Subnet4> ConstSubnet4Ptr;
+
+/// @brief A pointer to a @c Subnet4 object.
typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
-/// @brief A collection of @c Subnet4 objects
-///
-/// That is a simple vector of pointers. It does not make much sense to
-/// optimize access time (e.g. using a map), because typical search
-/// pattern will use calling inRange() method on each subnet until
-/// a match is found.
-typedef std::vector<Subnet4Ptr> Subnet4Collection;
/// @brief A configuration holder for IPv6 subnet.
///
@@ -617,7 +648,7 @@ public:
/// @param t2 rebind timer (in seconds)
/// @param preferred_lifetime preferred lifetime of leases (in seconds)
/// @param valid_lifetime preferred lifetime of leases (in seconds)
- /// @param id arbitraty subnet id, default value of 0 triggers
+ /// @param id arbitrary subnet id, default value of 0 triggers
/// autogeneration of subnet id
Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
@@ -626,7 +657,7 @@ public:
const Triplet<uint32_t>& valid_lifetime,
const SubnetID id = 0);
- /// @brief Returns preverred lifetime (in seconds)
+ /// @brief Returns preferred lifetime (in seconds)
///
/// @return a triplet with preferred lifetime
Triplet<uint32_t> getPreferred() const {
@@ -662,6 +693,11 @@ public:
return (rapid_commit_);
}
+ /// @brief Unparse a subnet object.
+ ///
+ /// @return A pointer to unparsed subnet configuration.
+ virtual data::ElementPtr toElement() const;
+
private:
/// @brief Returns default address for pool selection
@@ -693,11 +729,83 @@ private:
};
+/// @brief A const pointer to a @c Subnet6 object.
+typedef boost::shared_ptr<const Subnet6> ConstSubnet6Ptr;
+
/// @brief A pointer to a Subnet6 object
typedef boost::shared_ptr<Subnet6> Subnet6Ptr;
-/// @brief A collection of Subnet6 objects
-typedef std::vector<Subnet6Ptr> Subnet6Collection;
+/// @name Definition of the multi index container holding subnet information
+///
+//@{
+
+/// @brief Tag for the random access index.
+struct SubnetRandomAccessIndexTag { };
+
+/// @brief Tag for the index for searching by subnet identifier.
+struct SubnetSubnetIdIndexTag { };
+
+/// @brief Tag for the index for searching by subnet prefix.
+struct SubnetPrefixIndexTag { };
+
+/// @brief Multi index container holding subnets.
+///
+/// This multi index container can hold pointers to @ref Subnet4 or
+/// @ref Subnet6 objects representing subnets. It provides indexes for
+/// subnet lookups using subnet properties such as: subnet identifier
+/// or subnet prefix. It also provides a random access index which
+/// allows for using the container like a vector.
+///
+/// The random access index is used by the DHCP servers which perform
+/// a full scan on subnets to find the one that matches some specific
+/// criteria for subnet selection.
+///
+/// The remaining indexes are used for searching for a specific subnet
+/// as a result of receiving a command over the control API, e.g.
+/// when 'subnet-get' command is received.
+///
+/// @todo We should consider optimizing subnet selection by leveraging
+/// the indexing capabilities of this container, e.g. searching for
+/// a subnet by interface name, relay address etc.
+///
+/// @tparam SubnetType Type of the subnet: @ref Subnet4 or @ref Subnet6.
+template<typename SubnetType>
+using SubnetCollection = boost::multi_index_container<
+ // Multi index container holds pointers to the subnets.
+ boost::shared_ptr<SubnetType>,
+ // The following holds all indexes.
+ boost::multi_index::indexed_by<
+ // First is the random access index allowing for accessing
+ // objects just like we'd do with a vector.
+ boost::multi_index::random_access<
+ boost::multi_index::tag<SubnetRandomAccessIndexTag>
+ >,
+ // Second index allows for searching using subnet identifier.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetSubnetIdIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, SubnetID, &Subnet::getID>
+ >,
+ // Third index allows for searching using an output from toText function.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetPrefixIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
+ >
+ >
+>;
+
+/// @brief A collection of @c Subnet4 objects
+///
+/// This container provides a set of indexes which can be used to retrieve
+/// subnets by various properties.
+typedef SubnetCollection<Subnet4> Subnet4Collection;
+
+/// @brief A collection of @c Subnet6 objects
+///
+/// This container provides a set of indexes which can be used to retrieve
+/// subnets by various properties.
+typedef SubnetCollection<Subnet6> Subnet6Collection;
+
+//@}
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet_id.h b/src/lib/dhcpsrv/subnet_id.h
index 7fe6a2d078..4506d63df4 100644
--- a/src/lib/dhcpsrv/subnet_id.h
+++ b/src/lib/dhcpsrv/subnet_id.h
@@ -15,7 +15,7 @@ namespace dhcp {
/// @brief Defines unique IPv4 or IPv6 subnet identifier.
///
-/// Each subnet for which the DHCP service has been configured is identifed
+/// Each subnet for which the DHCP service has been configured is identified
/// by the unique value called subnet id. Right now it is represented as
/// a simple unsigned integer. In the future it may be extended to more complex
/// type.
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 8729de987d..97368608d0 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -160,6 +160,7 @@ libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
diff --git a/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc b/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
index 1b51fa5b19..f71c6a420d 100644
--- a/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
+++ b/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -244,6 +244,95 @@ TEST(AddrUtilitiesTest, addrsInRange6) {
isc::BadValue);
}
+// Checks if IPv4 address ranges can be converted to prefix / prefix_len
+TEST(AddrUtilitiesTest, prefixLengthFromRange4) {
+ // Use a shorter name
+ const auto& plfr = prefixLengthFromRange;
+
+ // Let's start with something simple
+ EXPECT_EQ(32, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.0")));
+ EXPECT_EQ(31, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.1")));
+ EXPECT_EQ(30, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.3")));
+ EXPECT_EQ(29, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.7")));
+ EXPECT_EQ(28, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.15")));
+ EXPECT_EQ(27, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.31")));
+ EXPECT_EQ(26, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.63")));
+ EXPECT_EQ(25, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.127")));
+ EXPECT_EQ(24, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.255")));
+ EXPECT_EQ(23, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.3.255")));
+ EXPECT_EQ(16, plfr(IOAddress("10.0.0.0"), IOAddress("10.0.255.255")));
+ EXPECT_EQ(8, plfr(IOAddress("10.0.0.0"), IOAddress("10.255.255.255")));
+ EXPECT_EQ(0, plfr(IOAddress("0.0.0.0"), IOAddress("255.255.255.255")));
+
+ // Fail if a network boundary is crossed
+ EXPECT_EQ(-1, plfr(IOAddress("10.0.0.255"), IOAddress("10.0.1.1")));
+
+ // The upper bound cannot be smaller than the lower bound
+ EXPECT_THROW(plfr(IOAddress("192.0.2.5"), IOAddress("192.0.2.4")),
+ isc::BadValue);
+}
+
+// Checks if IPv6 address ranges can be converted to prefix / prefix_len
+TEST(AddrUtilitiesTest, prefixLengthFromRange6) {
+ // Use a shorter name
+ const auto& plfr = prefixLengthFromRange;
+
+ // Let's start with something simple
+ EXPECT_EQ(128, plfr(IOAddress("::"), IOAddress("::")));
+ EXPECT_EQ(112, plfr(IOAddress("fe80::"), IOAddress("fe80::ffff")));
+ EXPECT_EQ(96, plfr(IOAddress("fe80::"), IOAddress("fe80::ffff:ffff")));
+ EXPECT_EQ(80, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::ffff:ffff:ffff")));
+ EXPECT_EQ(64, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(63, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::1:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(62, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::3:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(61, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::7:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(60, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::f:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(59, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::1f:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(58, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::3f:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(57, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::7f:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(56, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::ff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(55, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::1ff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(54, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::3ff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(53, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::7ff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(52, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::fff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(51, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::1fff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(50, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::3fff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(49, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::7fff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(48, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::ffff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(0, plfr(IOAddress("::"),
+ IOAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+
+ // Fail if a network boundary is crossed
+ EXPECT_EQ(-1, plfr(IOAddress("2001:db8::ffff"),
+ IOAddress("2001:db8::1:1")));
+
+ // The upper bound cannot be smaller than the lower bound
+ EXPECT_THROW(plfr(IOAddress("fe80::5"), IOAddress("fe80::4")),
+ isc::BadValue);
+
+ // Address family must match
+ EXPECT_THROW(plfr(IOAddress("192.0.2.0"), IOAddress("fe80::1")),
+ isc::BadValue);
+}
+
// Checks if prefixInRange returns valid number of prefixes in specified range.
TEST(AddrUtilitiesTest, prefixesInRange) {
diff --git a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
index 390ce44f97..0dc0559f63 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -44,7 +44,6 @@ TEST_F(AllocEngine4Test, constructor) {
EXPECT_THROW(x->getAllocator(Lease::TYPE_PD), BadValue);
}
-
// This test checks if the simple IPv4 allocation can succeed
TEST_F(AllocEngine4Test, simpleAlloc4) {
boost::scoped_ptr<AllocEngine> engine;
@@ -52,6 +51,9 @@ TEST_F(AllocEngine4Test, simpleAlloc4) {
100, false)));
ASSERT_TRUE(engine);
+ // Assigned addresses should be zero.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+
AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
false, true, "somehost.example.com.", false);
ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
@@ -72,6 +74,9 @@ TEST_F(AllocEngine4Test, simpleAlloc4) {
// Now check that the lease in LeaseMgr has the same parameters
detailCompareLease(lease, from_mgr);
+
+ // Assigned addresses should have incremented.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 1, subnet_->getID()));
}
// This test checks if the fake allocation (for DHCPDISCOVER) can succeed
@@ -81,6 +86,9 @@ TEST_F(AllocEngine4Test, fakeAlloc4) {
100, false)));
ASSERT_TRUE(engine);
+ // Assigned addresses should be zero.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+
AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
IOAddress("0.0.0.0"), false, true,
"host.example.com.", true);
@@ -100,6 +108,9 @@ TEST_F(AllocEngine4Test, fakeAlloc4) {
// Check that the lease is NOT in LeaseMgr
Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
ASSERT_FALSE(from_mgr);
+
+ // Assigned addresses should still be zero.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
}
@@ -267,7 +278,45 @@ TEST_F(AllocEngine4Test, allocateLease4Nulls) {
detailCompareLease(lease, from_mgr);
}
+// This test checks if a returning client can renew an
+// an existing lease and assigned-leases increments accordingly
+TEST_F(AllocEngine4Test, simpleRenew4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
+ 100, false)));
+ ASSERT_TRUE(engine);
+
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease);
+ checkLease4(lease);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ EXPECT_FALSE(ctx.old_lease_);
+
+ // We should have incremented assigned-addresses
+ EXPECT_TRUE(testStatistics("assigned-addresses", 1, subnet_->getID()));
+
+ // Do it again, this should amount to the renew of an existing lease
+ Lease4Ptr lease2 = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease2);
+ checkLease4(lease2);
+
+ // Lease already existed, so old_lease should be set.
+ EXPECT_TRUE(ctx.old_lease_);
+
+ // Should NOT have bumped assigned-addresses
+ EXPECT_TRUE(testStatistics("assigned-addresses", 1, subnet_->getID()));
+}
// This test verifies that the allocator picks addresses that belong to the
// pool
@@ -504,15 +553,19 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
IOAddress addr("192.0.2.105");
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+
// Just a different hw/client-id for the second client
uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+
Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2),
- sizeof(hwaddr2), 495, 100, 200, now,
- subnet_->getID()));
- // Make a copy of the lease, so as we can comapre that with the old lease
+ 495, 100, 200, now, subnet_->getID()));
+ // Make a copy of the lease, so as we can compare that with the old lease
// instance returned by the allocation engine.
Lease4 original_lease(*lease);
@@ -543,6 +596,13 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
// lease should be equal to the original lease.
ASSERT_TRUE(ctx.old_lease_);
EXPECT_TRUE(*ctx.old_lease_ == original_lease);
+
+ // Check that the stats declined stats were modified correctly. Note, because
+ // added the lease directly, assigned-leases never bumped to one, so when we
+ // reclaim it gets decremented to -1, then on assignment back to 0.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1, subnet_->getID()));
}
// This test checks if an expired declined lease can be reused when responding
@@ -599,35 +659,23 @@ TEST_F(AllocEngine4Test, discoverReuseDeclinedLease4Stats) {
pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
subnet_->addPool(pool_);
cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+ cfg_mgr.commit(); // so we will recalc stats
// Now create a declined lease, decline it and rewind its cltt, so it
// is expired.
Lease4Ptr declined = generateDeclinedLease("192.0.2.15", 100, -10);
- // Let's fix some global stats...
- StatsMgr& stats_mgr = StatsMgr::instance();
- stats_mgr.setValue("declined-addresses", static_cast<int64_t>(1000));
- stats_mgr.setValue("reclaimed-declined-addresses", static_cast<int64_t>(1000));
-
- // ...and subnet specific stats as well.
- string stat1 = StatsMgr::generateName("subnet", subnet_->getID(),
- "declined-addresses");
- string stat2 = StatsMgr::generateName("subnet", subnet_->getID(),
- "reclaimed-declined-addresses");
- stats_mgr.setValue(stat1, static_cast<int64_t>(1000));
- stats_mgr.setValue(stat2, static_cast<int64_t>(1000));
-
// Ask for any address. There's only one address in the pool, so it doesn't
// matter much.
Lease4Ptr assigned;
testReuseLease4(engine, declined, "0.0.0.0", true, SHOULD_PASS, assigned);
- // Check that the stats were not modified
- testStatistics("declined-addresses", 1000);
- testStatistics("reclaimed-declined-addresses", 1000);
-
- testStatistics(stat1, 1000);
- testStatistics(stat2, 1000);
+ // Check that the stats declined stats were not modified
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));
}
// This test checks if an expired declined lease can be reused when responding
@@ -682,39 +730,30 @@ TEST_F(AllocEngine4Test, requestReuseDeclinedLease4Stats) {
pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
subnet_->addPool(pool_);
cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+ cfg_mgr.commit();
// Now create a declined lease, decline it and rewind its cltt, so it
// is expired.
Lease4Ptr declined = generateDeclinedLease("192.0.2.15", 100, -10);
- // Let's fix some global stats...
- StatsMgr& stats_mgr = StatsMgr::instance();
- stats_mgr.setValue("declined-addresses", static_cast<int64_t>(1000));
- stats_mgr.setValue("reclaimed-declined-addresses", static_cast<int64_t>(1000));
-
- // ...and subnet specific stats as well.
- string stat1 = StatsMgr::generateName("subnet", subnet_->getID(),
- "declined-addresses");
- string stat2 = StatsMgr::generateName("subnet", subnet_->getID(),
- "reclaimed-declined-addresses");
- stats_mgr.setValue(stat1, static_cast<int64_t>(1000));
- stats_mgr.setValue(stat2, static_cast<int64_t>(1000));
-
// Asking specifically for this address
Lease4Ptr assigned;
testReuseLease4(engine, declined, "192.0.2.15", false, SHOULD_PASS, assigned);
// Check that we got it.
ASSERT_TRUE(assigned);
- // Check that the stats were modified
- testStatistics("declined-addresses", 999);
- testStatistics("reclaimed-declined-addresses", 1001);
-
- testStatistics(stat1, 999);
- testStatistics(stat2, 1001);
+ // Check that the stats are correct. Note that assigned-addresses does
+ // not get decremented when a lease is declined, ergo not incremented
+ // when it is reused. Declined address stats will be -1 since
+ // lease was created as declined which does not increment the stat.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("declined-addresses", -1));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1));
+ EXPECT_TRUE(testStatistics("declined-addresses", -1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1, subnet_->getID()));
}
-// This test checks that the Allocation Engine correcly identifies the
+// This test checks that the Allocation Engine correctly identifies the
// existing client's lease in the lease database, using the client
// identifier and HW address.
TEST_F(AllocEngine4Test, identifyClientLease) {
@@ -1652,8 +1691,7 @@ TEST_F(AllocEngine4Test, findReservation) {
ctx.addHostIdentifier(Host::IDENT_HWADDR, hwaddr_->hwaddr_);
ctx.addHostIdentifier(Host::IDENT_DUID, clientid_->getDuid());
- // There is no reservation in the database so no host should be
- // retruned.
+ // There is no reservation in the database so no host should be returned.
ASSERT_NO_THROW(engine.findReservation(ctx));
EXPECT_FALSE(ctx.host_);
diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
index e38c6b764d..aefd6d71e8 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -70,52 +70,48 @@ TEST_F(AllocEngine6Test, constructor) {
// This test checks if the simple allocation (REQUEST) can succeed
// and the stats counter is properly bumped by 1
TEST_F(AllocEngine6Test, simpleAlloc6) {
-
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
simpleAlloc6Test(pool_, IOAddress("::"), false);
- // We should have bumped the address counter by 1
- string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-nas");
- ObservationPtr stat = StatsMgr::instance().getObservation(name);
- ASSERT_TRUE(stat);
- EXPECT_EQ(101, stat->getInteger().first);
+ // We should have bumped the assigned counter by 1
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
}
// This test checks if the simple PD allocation (REQUEST) can succeed
// and the stats counter is properly bumped by 1
TEST_F(AllocEngine6Test, pdSimpleAlloc6) {
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-pds", 0, subnet_->getID()));
simpleAlloc6Test(pd_pool_, IOAddress("::"), false);
- // We should have bumped the address counter by 1
- string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-pds");
- ObservationPtr stat = StatsMgr::instance().getObservation(name);
- ASSERT_TRUE(stat);
- EXPECT_EQ(101, stat->getInteger().first);
+ // We should have bumped the assigned counter by 1
+ EXPECT_TRUE(testStatistics("assigned-pds", 1, subnet_->getID()));
}
// This test checks if the fake allocation (for SOLICIT) can succeed
// and the stats counter isn't bumped
TEST_F(AllocEngine6Test, fakeAlloc6) {
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
simpleAlloc6Test(pool_, IOAddress("::"), true);
- // We should not have bumped the address counter
- string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-nas");
- ObservationPtr stat = StatsMgr::instance().getObservation(name);
- ASSERT_TRUE(stat);
- EXPECT_EQ(100, stat->getInteger().first);
+ // We should not have bumped the assigned counter.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
}
// This test checks if the fake PD allocation (for SOLICIT) can succeed
// and the stats counter isn't bumped
TEST_F(AllocEngine6Test, pdFakeAlloc6) {
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-pds", 0, subnet_->getID()));
+
simpleAlloc6Test(pd_pool_, IOAddress("::"), true);
- // We should not have bumped the address counter
- string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-pds");
- ObservationPtr stat = StatsMgr::instance().getObservation(name);
- ASSERT_TRUE(stat);
- EXPECT_EQ(100, stat->getInteger().first);
+ // We should not have bumped the assigned counter
+ EXPECT_TRUE(testStatistics("assigned-pds", 0, subnet_->getID()));
};
// This test checks if the allocation with a hint that is valid (in range,
@@ -508,6 +504,11 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
// Initialize FQDN data for the lease.
initFqdn("myhost.example.com", true, true);
+ // Verify the none of relevant stats are zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+
// Just a different duid
DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
const uint32_t other_iaid = 3568;
@@ -545,6 +546,11 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
// Check that we got that single lease
ASSERT_TRUE(lease);
EXPECT_EQ(addr, lease->addr_);
+
+ // Verify the none of relevant stats were altered.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
}
// This test checks if an expired lease can be reused in REQUEST (actual allocation)
@@ -567,10 +573,12 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
// Let's create an expired lease
DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
const uint32_t other_iaid = 3568;
+
const SubnetID other_subnetid = 999;
Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
501, 502, 503, 504, other_subnetid, HWAddrPtr(),
0));
+
lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
lease->valid_lft_ = 495; // Lease was valid for 495 seconds
lease->fqdn_fwd_ = true;
@@ -578,10 +586,6 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
lease->hostname_ = "myhost.example.com.";
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
- // By default we pretend our subnet has 100 addresses
- string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-nas");
- StatsMgr::instance().setValue(name, static_cast<int64_t>(100));
-
// A client comes along, asking specifically for this address
AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
@@ -615,12 +619,12 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
// Now check that the lease in LeaseMgr has the same parameters
detailCompareLease(lease, from_mgr);
- // We should not have bumped the address counter
- // NOTE: when we start expiring addresses and removing them from
- // the stats this will no longer be true.
- ObservationPtr stat = StatsMgr::instance().getObservation(name);
- ASSERT_TRUE(stat);
- EXPECT_EQ(100, stat->getInteger().first);
+ // Verify the stats got adjusted correctly
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("assigned-nas", -1, other_subnetid));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1, other_subnetid));
}
// Checks if the lease lifetime is extended when the client sends the
@@ -776,18 +780,15 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolRequestNoHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
- // By default we pretend our subnet has 100 addresses
- string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-nas");
- StatsMgr::instance().setValue(name, static_cast<int64_t>(100));
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
ASSERT_TRUE(lease);
EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
- // We should have bumped the address counter
- ObservationPtr stat = StatsMgr::instance().getObservation(name);
- ASSERT_TRUE(stat);
- EXPECT_EQ(101, stat->getInteger().first);
+ // Assigned count should have been incremented by one.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
}
// Checks that a client gets the address reserved (in-pool case)
@@ -807,12 +808,18 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitValidHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
// Let's pretend the client sends hint 2001:db8:1::10.
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true);
ASSERT_TRUE(lease);
// The hint should be ignored and the reserved address should be assigned
EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // Assigned count should not have been incremented.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
}
// Checks that a client gets the address reserved (in-pool case)
@@ -832,12 +839,18 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolRequestValidHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
// Let's pretend the client sends hint 2001:db8:1::10.
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false);
ASSERT_TRUE(lease);
// The hint should be ignored and the reserved address should be assigned
EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // Assigned count should have been incremented.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
}
// Checks that a client gets the address reserved (in-pool case)
@@ -857,12 +870,18 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitMatchingHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
// Let's pretend the client sends hint 2001:db8:1::10.
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), true);
ASSERT_TRUE(lease);
// The hint should be ignored and the reserved address should be assigned
EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // Assigned count should not have been incremented.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
}
// Checks that a client gets the address reserved (in-pool case)
@@ -882,12 +901,18 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolRequestMatchingHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
// Let's pretend the client sends hint 2001:db8:1::10.
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), false);
ASSERT_TRUE(lease);
// The hint should be ignored and the reserved address should be assigned
EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // Assigned count should have been incremented.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
}
// Checks that a client gets the address reserved (out-of-pool case)
@@ -907,10 +932,16 @@ TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitNoHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), true, false);
ASSERT_TRUE(lease);
EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+ // Assigned count should not have been incremented.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
}
// Checks that a client gets the address reserved (out-of-pool case)
@@ -930,18 +961,15 @@ TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestNoHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
- // By default we pretend our subnet has 100 addresses
- string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-nas");
- StatsMgr::instance().setValue(name, static_cast<int64_t>(100));
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false, false);
ASSERT_TRUE(lease);
EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
// We should not have bumped the address counter
- ObservationPtr stat = StatsMgr::instance().getObservation(name);
- ASSERT_TRUE(stat);
- EXPECT_EQ(100, stat->getInteger().first);
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
}
// Checks that a client gets the address reserved (in-pool case)
@@ -961,12 +989,18 @@ TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitValidHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
// Let's pretend the client sends hint 2001:db8:1::10.
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true, false);
ASSERT_TRUE(lease);
// The hint should be ignored and the reserved address should be assigned
EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+
+ // We should not have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
}
// Checks that a client gets the address reserved (out-of-pool case)
@@ -986,12 +1020,18 @@ TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestValidHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
// Let's pretend the client sends hint 2001:db8:1::10.
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false, false);
ASSERT_TRUE(lease);
// The hint should be ignored and the reserved address should be assigned
EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+
+ // We should not have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
}
// Checks that a client gets the address reserved (out-of-pool case)
@@ -1011,12 +1051,18 @@ TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitMatchingHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
// Let's pretend the client sends hint 2001:db8:1::10.
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), true, false);
ASSERT_TRUE(lease);
// The hint should be ignored and the reserved address should be assigned
EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+
+ // We should not have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
}
// Checks that a client gets the address reserved (out-of-pool case)
@@ -1036,12 +1082,18 @@ TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestMatchingHint) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
// Let's pretend the client sends hint 2001:db8:1::10.
Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), false, false);
ASSERT_TRUE(lease);
// The hint should be ignored and the reserved address should be assigned
EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+
+ // We should not have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
}
// In the following situation:
@@ -1054,15 +1106,15 @@ TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestMatchingHint) {
TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedThis) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
// Client gets an address
Lease6Ptr lease1 = simpleAlloc6Test(pool_, IOAddress("::"), false);
ASSERT_TRUE(lease1);
// We should have bumped the address counter
- string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-nas");
- ObservationPtr stat = StatsMgr::instance().getObservation(name);
- ASSERT_TRUE(stat);
- EXPECT_EQ(101, stat->getInteger().first);
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
// Just check that if the client requests again, it will get the same
// address.
@@ -1071,7 +1123,7 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedThis) {
detailCompareLease(lease1, lease2);
// We should not have bumped the address counter again
- EXPECT_EQ(101, stat->getInteger().first);
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
// Now admin creates a reservation for this client. This is in-pool
// reservation, as the pool is 2001:db8:1::10 - 2001:db8:1::20.
@@ -1101,9 +1153,9 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedThis) {
// Now check that the lease in LeaseMgr has the same parameters
detailCompareLease(lease3, from_mgr);
- // Lastly check to see that the address counter is still 101 we should have
+ // Lastly check to see that the address counter is still 1, we should have
// have decremented it on the implied release and incremented it on the reserved
- EXPECT_EQ(101, stat->getInteger().first);
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
}
// In the following situation:
// - client X is assigned an address A
@@ -1114,15 +1166,15 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedThis) {
TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedOther) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
// Client gets an address
Lease6Ptr lease1 = simpleAlloc6Test(pool_, IOAddress("::"), false);
ASSERT_TRUE(lease1);
// We should have bumped the address counter
- string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-nas");
- ObservationPtr stat = StatsMgr::instance().getObservation(name);
- ASSERT_TRUE(stat);
- EXPECT_EQ(101, stat->getInteger().first);
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
// Just check that if the client requests again, it will get the same
// address.
@@ -1131,7 +1183,7 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedOther) {
detailCompareLease(lease1, lease2);
// We should not have bumped the address counter again
- EXPECT_EQ(101, stat->getInteger().first);
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
// Now admin creates a reservation for this client. Let's use the
// address client X just received. Let's generate a host, but don't add it
@@ -1143,8 +1195,7 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedOther) {
host->setIdentifier(&other_duid[0], other_duid.size(), Host::IDENT_DUID);
// Ok, now add it to the HostMgr
- CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
- CfgMgr::instance().commit();
+ addHost(host);
// Just check that this time the client will get a different lease.
Lease6Ptr lease3 = simpleAlloc6Test(pool_, lease1->addr_, false);
@@ -1167,9 +1218,9 @@ TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedOther) {
// Now check that the lease in LeaseMgr has the same parameters
detailCompareLease(lease3, from_mgr);
- // Lastly check to see that the address counter is still 101 we should have
+ // Lastly check to see that the address counter is still 1 we should have
// have decremented it on the implied release and incremented it on the reserved
- EXPECT_EQ(101, stat->getInteger().first);
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
}
// Checks that a reserved address for client A is not assigned when
@@ -1267,11 +1318,17 @@ TEST_F(AllocEngine6Test, allocateLeasesInvalidData) {
TEST_F(AllocEngine6Test, addressRenewal) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100);
+ // Assigned count should zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
Lease6Collection leases;
leases = allocateTest(engine, pool_, IOAddress("::"), false, true);
ASSERT_EQ(1, leases.size());
+ // Assigned count should be one.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+
// This is what the client will send in his renew message.
AllocEngine::HintContainer hints;
hints.push_back(make_pair(leases[0]->addr_, 128));
@@ -1287,6 +1344,9 @@ TEST_F(AllocEngine6Test, addressRenewal) {
EXPECT_EQ(leases[0]->type_, renewed[0]->type_);
EXPECT_EQ(leases[0]->preferred_lft_, renewed[0]->preferred_lft_);
EXPECT_EQ(leases[0]->valid_lft_, renewed[0]->valid_lft_);
+
+ // Assigned count should still be one.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
}
// Checks whether an address can be renewed (in-pool reservation)
@@ -1297,12 +1357,18 @@ TEST_F(AllocEngine6Test, reservedAddressRenewal) {
AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100);
+ // Assigned count should zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
Lease6Collection leases;
leases = allocateTest(engine, pool_, IOAddress("::"), false, true);
ASSERT_EQ(1, leases.size());
ASSERT_EQ("2001:db8:1::1c", leases[0]->addr_.toText());
+ // Assigned count should be one.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+
// This is what the client will send in his renew message.
AllocEngine::HintContainer hints;
hints.push_back(make_pair(leases[0]->addr_, 128));
@@ -1310,6 +1376,9 @@ TEST_F(AllocEngine6Test, reservedAddressRenewal) {
Lease6Collection renewed = renewTest(engine, pool_, hints, true);
ASSERT_EQ(1, renewed.size());
ASSERT_EQ("2001:db8:1::1c", leases[0]->addr_.toText());
+
+ // Assigned count should still be one.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
}
// Checks whether a single host can have more than one reservation.
@@ -1320,7 +1389,7 @@ TEST_F(AllocEngine6Test, reservedAddressRenewal) {
/// reservation for the second IA. This works for Requests and Renews, though.
/// In both of those messages, when processing of the first IA is complete,
/// we have a lease in the database. Based on that, when processing the second
-/// IA we can detect that the first reservated address is in use already and
+/// IA we can detect that the first reserved address is in use already and
/// use the second reservation.
TEST_F(AllocEngine6Test, DISABLED_reserved2AddressesSolicit) {
// Create reservation for the client. This is in-pool reservation,
@@ -1667,7 +1736,7 @@ TEST_F(AllocEngine6Test, largeAllocationAttemptsOverride) {
address << "2001:db8:1::";
address << i;
- // Allocate the leease.
+ // Allocate the lease.
Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress(address.str()),
duid, iaid, 501, 502, 503, 504, subnet_->getID(),
HWAddrPtr(), 0));
@@ -1771,37 +1840,32 @@ TEST_F(AllocEngine6Test, solicitReuseDeclinedLease6Stats) {
IOAddress addr(addr_txt);
initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+ // Stats should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));
+
+
// Now create a declined lease, decline it and rewind its cltt, so it
// is expired.
Lease6Ptr declined = generateDeclinedLease(addr_txt, 100, -10);
- // Let's fix some global stats...
- StatsMgr& stats_mgr = StatsMgr::instance();
- stats_mgr.setValue("declined-addresses", static_cast<int64_t>(1000));
- stats_mgr.setValue("reclaimed-declined-addresses", static_cast<int64_t>(1000));
-
- // ...and subnet specific stats as well.
- string stat1 = StatsMgr::generateName("subnet", subnet_->getID(),
- "declined-addresses");
- string stat2 = StatsMgr::generateName("subnet", subnet_->getID(),
- "reclaimed-declined-addresses");
- stats_mgr.setValue(stat1, static_cast<int64_t>(1000));
- stats_mgr.setValue(stat2, static_cast<int64_t>(1000));
-
// Ask for any address. There's only one address in the pool, so it doesn't
// matter much.
Lease6Ptr assigned;
testReuseLease6(engine, declined, "::", true, SHOULD_PASS, assigned);
// Check that the stats were not modified
- testStatistics("declined-addresses", 1000);
- testStatistics("reclaimed-declined-addresses", 1000);
-
- testStatistics(stat1, 1000);
- testStatistics(stat2, 1000);
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));
}
-// This test checks if statistics are not updated when expired declined lease
+// This test checks if statistics are updated when expired declined lease
// is reused when responding to REQUEST (actual allocation)
TEST_F(AllocEngine6Test, requestReuseDeclinedLease6Stats) {
@@ -1815,34 +1879,102 @@ TEST_F(AllocEngine6Test, requestReuseDeclinedLease6Stats) {
IOAddress addr(addr_txt);
initSubnet(IOAddress("2001:db8::"), addr, addr);
+ // Stats should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));
+
// Now create a declined lease, decline it and rewind its cltt, so it
// is expired.
Lease6Ptr declined = generateDeclinedLease(addr_txt, 100, -10);
- // Let's fix some global stats...
- StatsMgr& stats_mgr = StatsMgr::instance();
- stats_mgr.setValue("declined-addresses", static_cast<int64_t>(1000));
- stats_mgr.setValue("reclaimed-declined-addresses", static_cast<int64_t>(1000));
-
- // ...and subnet specific stats as well.
- string stat1 = StatsMgr::generateName("subnet", subnet_->getID(),
- "declined-addresses");
- string stat2 = StatsMgr::generateName("subnet", subnet_->getID(),
- "reclaimed-declined-addresses");
- stats_mgr.setValue(stat1, static_cast<int64_t>(1000));
- stats_mgr.setValue(stat2, static_cast<int64_t>(1000));
-
// Ask for any address. There's only one address in the pool, so it doesn't
// matter much.
Lease6Ptr assigned;
testReuseLease6(engine, declined, "::", false, SHOULD_PASS, assigned);
- // Check that the stats were not modified
- testStatistics("declined-addresses", 999);
- testStatistics("reclaimed-declined-addresses", 1001);
+ // Check that the stats were modified as expected.
+ // assigned-nas should NOT get incremented. Currently we do not adjust assigned
+ // counts when we declines
+ // declined-addresses will -1, as the artificial creation of declined lease
+ // doesn't increment it from zero. reclaimed-declined-addresses will be 1
+ // because the leases are implicitly reclaimed before they can be assigned.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("declined-addresses", -1));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1));
+ EXPECT_TRUE(testStatistics("declined-addresses", -1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1, subnet_->getID()));
+
+}
+
+// This test checks if an expired-reclaimed lease can be reused by
+// a returning client via REQUEST, rather than renew/rebind. This
+// would be typical of cable modem clients which do not retain lease
+// data across reboots.
+TEST_F(AllocEngine6Test, reuseReclaimedExpiredViaRequest) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
+ cfg_mgr.commit();
+
+ // Verify relevant stats are zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+
+ // Let's create an expired lease
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 501, 502, 503, 504, subnet_->getID(), HWAddrPtr(),
+ 0));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ lease->fqdn_fwd_ = true;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "myhost.example.com.";
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Verify that the lease state is indeed expired-reclaimed
+ EXPECT_EQ(lease->state_, Lease::STATE_EXPIRED_RECLAIMED);
+
+ // Same client comes along and issues a request
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+ // Check that he got the original lease back.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Verify that the lease state has been set back to the default.
+ EXPECT_EQ(lease->state_, Lease::STATE_DEFAULT);
- testStatistics(stat1, 999);
- testStatistics(stat2, 1001);
+ // Verify assigned-nas got bumped. Reclaimed stats should still
+ // be zero as we artificially marked it reclaimed.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
}
}; // namespace test
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc
index ad23d1e3cf..997fe9549b 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -48,7 +48,7 @@ const unsigned int TEST_LEASES_NUM = 100;
struct LowerBound {
/// @brief Constructor.
///
- /// @param lower_bound Interger value wrapped by the structure.
+ /// @param lower_bound Integer value wrapped by the structure.
explicit LowerBound(const size_t lower_bound)
: lower_bound_(lower_bound) { };
@@ -70,7 +70,7 @@ struct LowerBound {
struct UpperBound {
/// @brief Constructor.
///
- /// @param lower_bound Interger value wrapped by the structure.
+ /// @param lower_bound Integer value wrapped by the structure.
explicit UpperBound(const size_t upper_bound)
: upper_bound_(upper_bound) { };
@@ -98,7 +98,7 @@ std::string callout_argument_name("lease4");
/// - it processes multiple leases,
/// - leases are processed in certain order,
/// - number of processed leases may be limited by the parameters,
-/// - maxium duration of the lease reclamation routine may be limited,
+/// - maximum duration of the lease reclamation routine may be limited,
/// - reclaimed leases may be marked as reclaimed or deleted,
/// - DNS records for some of the leases must be removed when the lease
/// is reclaimed and DNS updates are enabled,
@@ -132,7 +132,7 @@ std::string callout_argument_name("lease4");
/// to mark leases with even indexes as expired and then test whether
/// leases with even indexes have been successfully reclaimed.
///
-/// The "lease algorithm" verifies if the given lease fulfils the
+/// The "lease algorithm" verifies if the given lease fulfills the
/// specific conditions after reclamation. These are the examples of
/// the lease algorithms:
/// - leaseExists - lease still exists in the database,
@@ -144,7 +144,7 @@ std::string callout_argument_name("lease4");
/// - dnsUpdateNotGeneratedForLease - DNS updates not generated for lease
///
/// The combination of index algorithm and lease algorithm allows for
-/// verifying that the whole sets of leases in the lease database fulfil
+/// verifying that the whole sets of leases in the lease database fulfill
/// certain conditions. For example, it is possible to verify that
/// after lease reclamation leases with even indexes have state set to
/// "expired-reclaimed".
@@ -304,7 +304,7 @@ public:
/// @brief Wrapper method running lease reclamation routine.
///
/// @param max_leases Maximum number of leases to be reclaimed.
- /// @param timeout Maximum amount of time that the reclaimation routine
+ /// @param timeout Maximum amount of time that the reclamation routine
/// may be processing expired leases, expressed in seconds.
/// @param remove_lease A boolean value indicating if the lease should
/// be removed when it is reclaimed (if true) or it should be left in the
@@ -359,7 +359,7 @@ public:
LeasePtrType lease = getLease(i);
// index_algorithm(i) checks if the lease should be checked.
// If so, check if the lease_algorithm indicates that the
- // lease fulfils a given condition, e.g. is present in the
+ // lease fulfills a given condition, e.g. is present in the
// database. If not, return false.
if (index_algorithm(i) && !lease_algorithm(lease)) {
return (false);
@@ -654,7 +654,7 @@ public:
/// of reclaimed leases.
void testReclaimExpiredLeasesLimit() {
for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
- // Mark all leaes as expired. The higher the index the less
+ // Mark all leases as expired. The higher the index the less
// expired the lease.
expire(i, 1000 - i);
}
@@ -783,7 +783,7 @@ public:
<< "check failed for i = " << i;
- // At early stages of iterations, there should be conitnuous
+ // At early stages of iterations, there should be continuous
// group of leases (expired and not expired) which haven't been
// reclaimed.
if (reclaimed_lower_bound > 0) {
@@ -811,7 +811,7 @@ public:
if (evenLeaseIndex(i)) {
// Hostname with two consecutive dots is invalid and may result
// in exception if the reclamation routine doesn't protect
- // aginst such exceptions.
+ // against such exceptions.
std::ostringstream hostname_s;
hostname_s << "invalid-host" << i << "..example.com";
leases_[i]->hostname_ = hostname_s.str();
@@ -838,7 +838,7 @@ public:
EXPECT_TRUE(testLeases(&dnsUpdateGeneratedForLease, &oddLeaseIndex));
}
- /// @brief This test verfies that callouts are executed for each expired
+ /// @brief This test verifies that callouts are executed for each expired
/// lease when installed.
void testReclaimExpiredLeasesHooks() {
for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
@@ -868,7 +868,7 @@ public:
EXPECT_TRUE(testLeases(&leaseNotReclaimed, &oddLeaseIndex));
}
- /// @brief This test verfies that callouts are executed for each expired
+ /// @brief This test verifies that callouts are executed for each expired
/// lease and that the lease is not reclaimed when skip flag is set.
void testReclaimExpiredLeasesHooksWithSkip() {
for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
@@ -1000,11 +1000,11 @@ public:
///
/// This method works for both v4 and v6. Just make sure the correct
/// statistic name is passed. This is the name of the assigned addresses,
- /// that is expected to be decreased once the reclaimation procedure
+ /// that is expected to be decreased once the reclamation procedure
/// is complete.
///
/// @param stat_name name of the statistic for assigned addresses statistic
- /// ("assgined-addresses" for both v4 and "assigned-nas" for v6)
+ /// ("assigned-addresses" for both v4 and "assigned-nas" for v6)
void testReclaimDeclinedStats(const std::string& stat_name) {
// Leases by default all belong to subnet_id_ = 1. Let's count the
@@ -1072,8 +1072,8 @@ public:
// subnet[X].assigned-addresses should go down. Between the time
// of DHCPDECLINE(v4)/DECLINE(v6) reception and declined expired lease
- // reclaimation, we count this address as assigned-addresses. We decrease
- // assigned-addresses(v4)/assgined-nas(v6) when we reclaim the lease,
+ // reclamation, we count this address as assigned-addresses. We decrease
+ // assigned-addresses(v4)/assigned-nas(v6) when we reclaim the lease,
// not when the packet is received. For explanation, see Duplicate
// Addresses (DHCPDECLINE support) (v4) or Duplicate Addresses (DECLINE
// support) sections in the User's Guide or a comment in
@@ -1164,7 +1164,7 @@ public:
/// @brief Wrapper method running lease reclamation routine.
///
/// @param max_leases Maximum number of leases to be reclaimed.
- /// @param timeout Maximum amount of time that the reclaimation routine
+ /// @param timeout Maximum amount of time that the reclamation routine
/// may be processing expired leases, expressed in seconds.
/// @param remove_lease A boolean value indicating if the lease should
/// be removed when it is reclaimed (if true) or it should be left in the
@@ -1349,7 +1349,7 @@ ExpirationAllocEngine6Test::testReclaimExpiredLeasesStats() {
BOOST_STATIC_ASSERT(TEST_LEASES_NUM % 2 == 0);
for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
- // Mark all leaes as expired. The higher the index the less
+ // Mark all leases as expired. The higher the index the less
// expired the lease.
expire(i, 1000 - i);
// Modify subnet ids and lease types for some leases.
@@ -1437,13 +1437,12 @@ ExpirationAllocEngine6Test::testReclaimReusedLeases(const uint16_t msg_type,
// initially reclaimed.
if (use_reclaimed || (msg_type == DHCPV6_SOLICIT)) {
EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
-
} else {
EXPECT_TRUE(testStatistics("reclaimed-leases", TEST_LEASES_NUM));
+ EXPECT_TRUE(testStatistics("assigned-nas", TEST_LEASES_NUM, subnet->getID()));
// Leases should have been updated in the lease database and their
// state should not be 'expired-reclaimed' anymore.
EXPECT_TRUE(testLeases(&leaseNotReclaimed, &allLeaseIndexes));
-
}
}
@@ -1707,7 +1706,7 @@ public:
/// @brief Wrapper method running lease reclamation routine.
///
/// @param max_leases Maximum number of leases to be reclaimed.
- /// @param timeout Maximum amount of time that the reclaimation routine
+ /// @param timeout Maximum amount of time that the reclamation routine
/// may be processing expired leases, expressed in seconds.
/// @param remove_lease A boolean value indicating if the lease should
/// be removed when it is reclaimed (if true) or it should be left in the
@@ -2002,7 +2001,7 @@ ExpirationAllocEngine4Test::testReclaimExpiredLeasesStats() {
BOOST_STATIC_ASSERT(TEST_LEASES_NUM % 2 == 0);
for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
- // Mark all leaes as expired. The higher the index the less
+ // Mark all leases as expired. The higher the index the less
// expired the lease.
expire(i, 1000 - i);
// Modify subnet ids of some leases.
@@ -2099,6 +2098,7 @@ ExpirationAllocEngine4Test::testReclaimReusedLeases(const uint8_t msg_type,
} else if (msg_type == DHCPREQUEST) {
// Re-allocation of expired leases should result in reclamations.
EXPECT_TRUE(testStatistics("reclaimed-leases", TEST_LEASES_NUM));
+ EXPECT_TRUE(testStatistics("assigned-addresses", TEST_LEASES_NUM, subnet->getID()));
// Leases should have been updated in the lease database and their
// state should not be 'expired-reclaimed' anymore.
EXPECT_TRUE(testLeases(&leaseNotReclaimed, &allLeaseIndexes));
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc
index ae02064506..1aa2ed2abf 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -46,6 +46,7 @@ public:
callback_addr_updated_ = IOAddress("::");
callback_qry_pkt6_.reset();
callback_qry_options_copy_ = false;
+ callback_skip_ = 0;
}
/// callback that stores received callout name and received values
@@ -91,6 +92,21 @@ public:
return (0);
}
+ /// callback that return next step skip status
+ static int
+ lease6_select_skip_callout(CalloutHandle& callout_handle) {
+
+ // Let's call the basic callout, so it can record all parameters
+ lease6_select_callout(callout_handle);
+
+ // Count the call
+ callback_skip_++;
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (0);
+ }
+
// Values to be used in callout to override lease6 content
static const IOAddress addr_override_;
static const uint32_t t1_override_;
@@ -110,9 +126,12 @@ public:
static vector<string> callback_argument_names_;
static Pkt6Ptr callback_qry_pkt6_;
static bool callback_qry_options_copy_;
+
+ // Counter for next step skip (should be not retried)
+ static unsigned callback_skip_;
};
-// For some reason intialization within a class makes the linker confused.
+// For some reason initialization within a class makes the linker confused.
// linker complains about undefined references if they are defined within
// the class declaration.
const IOAddress HookAllocEngine6Test::addr_override_("2001:db8::abcd");
@@ -132,6 +151,8 @@ vector<string> HookAllocEngine6Test::callback_argument_names_;
Pkt6Ptr HookAllocEngine6Test::callback_qry_pkt6_;
bool HookAllocEngine6Test::callback_qry_options_copy_;
+unsigned HookAllocEngine6Test::callback_skip_;
+
// This test checks if the lease6_select callout is executed and expected
// parameters as passed.
TEST_F(HookAllocEngine6Test, lease6_select) {
@@ -153,7 +174,7 @@ TEST_F(HookAllocEngine6Test, lease6_select) {
HookLibsCollection libraries; // no libraries at this time
HooksManager::loadLibraries(libraries);
- // Install pkt6_receive_callout
+ // Install lease6_select
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"lease6_select", lease6_select_callout));
@@ -192,8 +213,8 @@ TEST_F(HookAllocEngine6Test, lease6_select) {
EXPECT_FALSE(callback_fake_allocation_);
// Check if all expected parameters are reported. The order needs to be
- // alphapbetical to match the order returned by
- // CallbackHandle::getAgrumentNames()
+ // alphabetical to match the order returned by
+ // CallbackHandle::getArgumentNames()
vector<string> expected_argument_names;
expected_argument_names.push_back("fake_allocation");
expected_argument_names.push_back("lease6");
@@ -263,6 +284,36 @@ TEST_F(HookAllocEngine6Test, change_lease6_select) {
EXPECT_EQ(valid_override_, from_mgr->valid_lft_);
}
+// This test checks if lease6_select callout can set the status to next
+// step skip without the engine to retry.
+TEST_F(HookAllocEngine6Test, skip_lease6_select) {
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ HookLibsCollection libraries; // no libraries at this time
+ HooksManager::loadLibraries(libraries);
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_select", lease6_select_skip_callout));
+
+ // Call allocateLeases6. Callouts should be triggered here.
+ Lease6Ptr lease;
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)),
+ HooksManager::createCalloutHandle());
+ ctx.currentIA().iaid_ = iaid_;
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ // Check that we got no lease
+ EXPECT_FALSE(lease);
+
+ // Check no retry was attempted
+ EXPECT_EQ(1, callback_skip_);
+}
/// @brief helper class used in Hooks testing in AllocEngine4
///
@@ -294,6 +345,7 @@ public:
callback_addr_updated_ = IOAddress("::");
callback_qry_pkt4_.reset();
callback_qry_options_copy_ = false;
+ callback_skip_ = 0;
}
/// callback that stores received callout name and received values
@@ -338,6 +390,21 @@ public:
return (0);
}
+ /// callback that return next step skip status
+ static int
+ lease4_select_skip_callout(CalloutHandle& callout_handle) {
+
+ // Let's call the basic callout, so it can record all parameters
+ lease4_select_callout(callout_handle);
+
+ // Count the call
+ callback_skip_++;
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (0);
+ }
+
// Values to be used in callout to override lease4 content
static const IOAddress addr_override_;
static const uint32_t t1_override_;
@@ -356,9 +423,12 @@ public:
static vector<string> callback_argument_names_;
static Pkt4Ptr callback_qry_pkt4_;
static bool callback_qry_options_copy_;
+
+ // Counter for next step skip (should be not retried)
+ static unsigned callback_skip_;
};
-// For some reason intialization within a class makes the linker confused.
+// For some reason initialization within a class makes the linker confused.
// linker complains about undefined references if they are defined within
// the class declaration.
const IOAddress HookAllocEngine4Test::addr_override_("192.0.3.1");
@@ -377,6 +447,8 @@ vector<string> HookAllocEngine4Test::callback_argument_names_;
Pkt4Ptr HookAllocEngine4Test::callback_qry_pkt4_;
bool HookAllocEngine4Test::callback_qry_options_copy_;
+unsigned HookAllocEngine4Test::callback_skip_;
+
// This test checks if the lease4_select callout is executed and expected
// parameters as passed.
TEST_F(HookAllocEngine4Test, lease4_select) {
@@ -399,7 +471,7 @@ TEST_F(HookAllocEngine4Test, lease4_select) {
HookLibsCollection libraries; // no libraries at this time
HooksManager::loadLibraries(libraries);
- // Install pkt4_receive_callout
+ // Install lease4_select
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"lease4_select", lease4_select_callout));
@@ -440,8 +512,8 @@ TEST_F(HookAllocEngine4Test, lease4_select) {
EXPECT_EQ(callback_fake_allocation_, false);
// Check if all expected parameters are reported. The order needs to be
- // alphapbetical to match the order returned by
- // CallbackHandle::getAgrumentNames()
+ // alphabetical to match the order returned by
+ // CallbackHandle::getArgumentNames()
vector<string> expected_argument_names;
expected_argument_names.push_back("fake_allocation");
expected_argument_names.push_back("lease4");
@@ -510,6 +582,41 @@ TEST_F(HookAllocEngine4Test, change_lease4_select) {
EXPECT_EQ(valid_override_, from_mgr->valid_lft_);
}
+// This test checks if lease4_select callout can set the status to next
+// step skip without the engine to retry.
+TEST_F(HookAllocEngine4Test, skip_lease4_select) {
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
+ 100, false)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ HookLibsCollection libraries; // no libraries at this time
+ HooksManager::loadLibraries(libraries);
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_select", lease4_select_skip_callout));
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"),
+ false, false, "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ ctx.callout_handle_ = callout_handle;
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got no lease
+ EXPECT_FALSE(lease);
+
+ // Check no retry was attempted
+ EXPECT_EQ(1, callback_skip_);
+}
+
}; // namespace test
}; // namespace dhcp
}; // namespace isc
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.cc b/src/lib/dhcpsrv/tests/alloc_engine_utils.cc
index a38fb8970b..dc495a2e3e 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_utils.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -39,20 +39,23 @@ namespace isc {
namespace dhcp {
namespace test {
-bool testStatistics(const std::string& stat_name, const int64_t exp_value) {
+bool testStatistics(const std::string& stat_name, const int64_t exp_value,
+ const SubnetID subnet_id) {
try {
- ObservationPtr observation = StatsMgr::instance().getObservation(stat_name);
+ std::string name = (!subnet_id ? stat_name :
+ StatsMgr::generateName("subnet", subnet_id, stat_name));
+ ObservationPtr observation = StatsMgr::instance().getObservation(name);
if (observation) {
if (observation->getInteger().first != exp_value) {
ADD_FAILURE()
<< "value of the observed statistics '"
- << stat_name << "' " << "("
+ << name << "' " << "("
<< observation->getInteger().first << ") "
<< "doesn't match expected value (" << exp_value << ")";
}
return (observation->getInteger().first == exp_value);
} else {
- ADD_FAILURE() << "Expected statistic " << stat_name
+ ADD_FAILURE() << "Expected statistic " << name
<< " not found.";
}
@@ -136,7 +139,6 @@ AllocEngine6Test::AllocEngine6Test() {
// hardcoded anywhere.
const uint8_t mac[] = { 0, 1, 22, 33, 44, 55};
hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
-
// Initialize a subnet and short address pool.
initSubnet(IOAddress("2001:db8:1::"),
IOAddress("2001:db8:1::10"),
@@ -145,7 +147,6 @@ AllocEngine6Test::AllocEngine6Test() {
64, 80);
initFqdn("", false, false);
-
}
void
@@ -170,14 +171,6 @@ AllocEngine6Test::initSubnet(const asiolink::IOAddress& subnet,
cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
cfg_mgr.commit();
-
- // By default we pretend our subnet has 100 addresses and prefixes allocated.
- StatsMgr::instance().setValue(
- StatsMgr::generateName("subnet", subnet_->getID(), "assigned-nas"),
- static_cast<int64_t>(100));
- StatsMgr::instance().setValue(
- StatsMgr::generateName("subnet", subnet_->getID(), "assigned-pds"),
- static_cast<int64_t>(100));
}
void
@@ -536,6 +529,8 @@ AllocEngine4Test::initSubnet(const asiolink::IOAddress& pool_start,
AllocEngine4Test::AllocEngine4Test() {
+ CfgMgr::instance().clear();
+
// This lease mgr needs to exist to before configuration commits.
factory_.create("type=memfile universe=4 persist=false");
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.h b/src/lib/dhcpsrv/tests/alloc_engine_utils.h
index 5bf68748e3..7f4554b04e 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_utils.h
+++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.h
@@ -1,7 +1,6 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
+// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef LIBDHCPSRV_ALLOC_ENGINE_UTILS_H
@@ -39,10 +38,12 @@ namespace test {
///
/// @param stat_name Statistic name.
/// @param exp_value Expected value.
+/// @param subnet_id subnet_id of the desired subnet, if not zero
///
/// @return true if the statistic manager holds a particular value,
/// false otherwise.
-bool testStatistics(const std::string& stat_name, const int64_t exp_value);
+bool testStatistics(const std::string& stat_name, const int64_t exp_value,
+ const SubnetID subnet_id = 0);
/// @brief Allocation engine with some internal methods exposed
class NakedAllocEngine : public AllocEngine {
@@ -67,7 +68,7 @@ public:
public:
/// @brief constructor
- /// @param type pool types that will be interated
+ /// @param type pool types that will be iterated through
NakedIterativeAllocator(Lease::Type type)
:IterativeAllocator(type) {
}
@@ -131,7 +132,7 @@ public:
fqdn_rev_ = fqdn_rev;
}
- /// @brief Wrapper around call to AllocEngine6::findRervation
+ /// @brief Wrapper around call to AllocEngine6::findReservation
///
/// If a reservation is found by the engine, the function sets
/// ctx.hostname_ accordingly.
@@ -360,12 +361,26 @@ public:
host->addReservation(resv);
if (add_to_host_mgr) {
- CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
- CfgMgr::instance().commit();
+ addHost(host);
}
+
return (host);
}
+ /// @brief Add a host reservation to the current configuration
+ ///
+ /// Adds the given host reservation to the current configuration by
+ /// casting it to non-const. We do it this way rather than adding it to
+ /// staging and then committing as that wipes out the current configuration
+ /// such as subnets.
+ ///
+ /// @param host host reservation to add
+ void
+ addHost(HostPtr& host) {
+ SrvConfigPtr cfg = boost::const_pointer_cast<SrvConfig>(CfgMgr::instance().getCurrentCfg());
+ cfg->getCfgHosts()->add(host);
+ }
+
/// @brief Utility function that creates a host reservation (hwaddr)
///
/// @param add_to_host_mgr true if the reservation should be added
diff --git a/src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc b/src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc
index 59e463c30d..a22aa61530 100644
--- a/src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc
+++ b/src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -73,7 +73,7 @@ TEST(CalloutHandleStoreTest, StoreRetrieve) {
EXPECT_FALSE(chptr_1 == chptr_2);
// Check reference counts. The getCalloutHandle function should be storing
- // pointers to the objects poiunted to by chptr_2 and pktptr_2.
+ // pointers to the objects pointed to by chptr_2 and pktptr_2.
EXPECT_EQ(1, chptr_1.use_count());
EXPECT_EQ(1, pktptr_1.use_count());
EXPECT_EQ(2, chptr_2.use_count());
@@ -94,7 +94,7 @@ TEST(CalloutHandleStoreTest, StoreRetrieve) {
EXPECT_EQ(1, pktptr_2.use_count());
}
-// The followings is a trival test to check that if the template function
+// The followings is a trivial test to check that if the template function
// is referred to in a separate compilation unit, only one copy of the static
// objects stored in it are returned. (For a change, we'll use a Pkt6 as the
// packet object.)
diff --git a/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc b/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc
index 80e39370b1..cc8361ae50 100644
--- a/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc
@@ -1,21 +1,24 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
+#include <cc/data.h>
#include <dhcpsrv/cfg_db_access.h>
#include <dhcpsrv/host_data_source_factory.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/testutils/mysql_schema.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
using namespace isc;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
+using namespace isc::test;
namespace {
@@ -23,7 +26,11 @@ namespace {
TEST(CfgDbAccessTest, defaults) {
CfgDbAccess cfg;
EXPECT_EQ("type=memfile", cfg.getLeaseDbAccessString());
+ std::string expected = "{ \"type\": \"memfile\" }";
+ runToElementTest<CfgLeaseDbAccess>(expected, CfgLeaseDbAccess(cfg));
+
EXPECT_TRUE(cfg.getHostDbAccessString().empty());
+ runToElementTest<CfgHostDbAccess>("{ }", CfgHostDbAccess(cfg));
}
// This test verifies that it is possible to set the lease database
@@ -33,10 +40,17 @@ TEST(CfgDbAccessTest, setLeaseDbAccessString) {
ASSERT_NO_THROW(cfg.setLeaseDbAccessString("type=mysql"));
EXPECT_EQ("type=mysql", cfg.getLeaseDbAccessString());
+ // Check unparse
+ std::string expected = "{ \"type\": \"mysql\" }";
+ runToElementTest<CfgLeaseDbAccess>(expected, CfgLeaseDbAccess(cfg));
+
// Append additional parameter.
ASSERT_NO_THROW(cfg.setAppendedParameters("universe=4"));
EXPECT_EQ("type=mysql universe=4", cfg.getLeaseDbAccessString());
+ // Additional parameters are not in lease_db_access_
+ runToElementTest<CfgLeaseDbAccess>(expected, CfgLeaseDbAccess(cfg));
+
// If access string is empty, no parameters will be appended.
ASSERT_NO_THROW(cfg.setLeaseDbAccessString(""));
EXPECT_TRUE(cfg.getLeaseDbAccessString().empty());
@@ -50,10 +64,17 @@ TEST(CfgDbAccessTest, setHostDbAccessString) {
ASSERT_NO_THROW(cfg.setHostDbAccessString("type=mysql"));
EXPECT_EQ("type=mysql", cfg.getHostDbAccessString());
+ // Check unparse
+ std::string expected = "{ \"type\": \"mysql\" }";
+ runToElementTest<CfgHostDbAccess>(expected, CfgHostDbAccess(cfg));
+
// Append additional parameter.
ASSERT_NO_THROW(cfg.setAppendedParameters("universe=4"));
EXPECT_EQ("type=mysql universe=4", cfg.getHostDbAccessString());
+ // Additional parameters are not in host_db_access_
+ runToElementTest<CfgHostDbAccess>(expected, CfgHostDbAccess(cfg));
+
// If access string is empty, no parameters will be appended.
ASSERT_NO_THROW(cfg.setHostDbAccessString(""));
EXPECT_TRUE(cfg.getHostDbAccessString().empty());
diff --git a/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc b/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc
index cb83b097f0..df9c7daf77 100644
--- a/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
#include <dhcpsrv/cfg_duid.h>
#include <exceptions/exceptions.h>
#include <testutils/io_utils.h>
+#include <testutils/test_to_element.h>
#include <util/encode/hex.h>
#include <gtest/gtest.h>
#include <stdint.h>
@@ -20,6 +21,7 @@
using namespace isc;
using namespace isc::dhcp;
+using namespace isc::test;
namespace {
@@ -92,6 +94,14 @@ TEST_F(CfgDUIDTest, defaults) {
EXPECT_EQ(0, cfg_duid.getTime());
EXPECT_EQ(0, cfg_duid.getEnterpriseId());
EXPECT_TRUE(cfg_duid.persist());
+
+ std::string expected = "{ \"type\": \"LLT\",\n"
+ "\"identifier\": \"\",\n"
+ "\"htype\": 0,\n"
+ "\"time\": 0,\n"
+ "\"enterprise-id\": 0,\n"
+ "\"persist\": true }";
+ runToElementTest<CfgDUID>(expected, cfg_duid);
}
// This test verifies that it is possible to set values for the CfgDUID.
@@ -112,6 +122,14 @@ TEST_F(CfgDUIDTest, setValues) {
EXPECT_EQ(32100, cfg_duid.getTime());
EXPECT_EQ(10, cfg_duid.getEnterpriseId());
EXPECT_FALSE(cfg_duid.persist());
+
+ std::string expected = "{ \"type\": \"EN\",\n"
+ " \"identifier\": \"ABCDEF\",\n"
+ " \"htype\": 100,\n"
+ " \"time\": 32100,\n"
+ " \"enterprise-id\": 10,\n"
+ " \"persist\": false }";
+ runToElementTest<CfgDUID>(expected, cfg_duid);
}
// This test checks positive scenarios for setIdentifier.
@@ -165,7 +183,7 @@ TEST_F(CfgDUIDTest, createLLT) {
duid->toText());
// Verify that the DUID file has been created.
- EXPECT_TRUE(dhcp::test::fileExists(absolutePath(DUID_FILE_NAME)));
+ EXPECT_TRUE(fileExists(absolutePath(DUID_FILE_NAME)));
}
// This method checks that the DUID-EN can be created from the
@@ -185,7 +203,7 @@ TEST_F(CfgDUIDTest, createEN) {
EXPECT_EQ("00:02:00:00:10:10:25:0f:3e:26:a7:62", duid->toText());
// Verify that the DUID file has been created.
- EXPECT_TRUE(dhcp::test::fileExists(absolutePath(DUID_FILE_NAME)));
+ EXPECT_TRUE(fileExists(absolutePath(DUID_FILE_NAME)));
}
// This method checks that the DUID-LL can be created from the
@@ -205,7 +223,7 @@ TEST_F(CfgDUIDTest, createLL) {
EXPECT_EQ("00:03:00:02:12:41:34:a4:b3:67", duid->toText());
// Verify that the DUID file has been created.
- EXPECT_TRUE(dhcp::test::fileExists(absolutePath(DUID_FILE_NAME)));
+ EXPECT_TRUE(fileExists(absolutePath(DUID_FILE_NAME)));
}
// This test verifies that it is possible to disable storing
@@ -226,7 +244,7 @@ TEST_F(CfgDUIDTest, createDisableWrite) {
EXPECT_EQ("00:02:00:00:10:10:25:0f:3e:26:a7:62", duid->toText());
// DUID persistence is disabled so there should be no DUID file.
- EXPECT_FALSE(dhcp::test::fileExists(absolutePath(DUID_FILE_NAME)));
+ EXPECT_FALSE(fileExists(absolutePath(DUID_FILE_NAME)));
}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc b/src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc
index 0c106f3881..53468cd99a 100644
--- a/src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc
@@ -1,23 +1,24 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
-#include <dhcp/iface_mgr.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
#include <dhcpsrv/cfg_expiration.h>
#include <dhcpsrv/timer_mgr.h>
#include <exceptions/exceptions.h>
-#include <util/stopwatch.h>
+#include <testutils/test_to_element.h>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <gtest/gtest.h>
#include <stdint.h>
using namespace isc;
+using namespace isc::asiolink;
using namespace isc::dhcp;
-using namespace isc::util;
namespace {
@@ -113,6 +114,19 @@ TEST(CfgExpirationTest, defaults) {
cfg.getUnwarnedReclaimCycles());
}
+/// @brief Tests that unparse returns an expected value
+TEST(CfgExpirationTest, unparse) {
+ CfgExpiration cfg;
+ std::string defaults = "{\n"
+ "\"reclaim-timer-wait-time\": 10,\n"
+ "\"flush-reclaimed-timer-wait-time\": 25,\n"
+ "\"hold-reclaimed-time\": 3600,\n"
+ "\"max-reclaim-leases\": 100,\n"
+ "\"max-reclaim-time\": 250,\n"
+ "\"unwarned-reclaim-cycles\": 5 }";
+ isc::test::runToElementTest<CfgExpiration>(defaults, cfg);
+}
+
// Test the {get,set}ReclaimTimerWaitTime.
TEST(CfgExpirationTest, getReclaimTimerWaitTime) {
testAccessModify<uint16_t>(CfgExpiration::LIMIT_RECLAIM_TIMER_WAIT_TIME,
@@ -163,14 +177,14 @@ TEST(CfgExpirationTest, getUnwarnedReclaimCycles) {
/// but instead they record the number of calls to them and the parameters
/// with which they were executed. This allows for checking if the
/// @c CfgExpiration object calls the leases reclamation routine with the
-/// appropriate parameteres.
+/// appropriate parameters.
class LeaseReclamationStub {
public:
/// @brief Collection of parameters with which the @c reclaimExpiredLeases
/// method is called.
///
- /// Examination of these values allows for assesment if the @c CfgExpiration
+ /// Examination of these values allows for assessment if the @c CfgExpiration
/// calls the routine with the appropriate values.
struct RecordedParams {
/// @brief Maximum number of leases to be processed.
@@ -233,7 +247,7 @@ public:
/// expired-reclaimed leases.
///
/// @param secs Specifies the minimum amount of time, expressed in
- /// seconds, that must elapse before the expired-reclaime lease is
+ /// seconds, that must elapse before the expired-reclaimed lease is
/// deleted from the database.
void
deleteReclaimedLeases(const uint32_t secs) {
@@ -256,7 +270,7 @@ public:
/// @brief Structure holding values of parameters with which the
/// @c reclaimExpiredLeases was called.
///
- /// These values are overriden on subsequent calls to this method.
+ /// These values are overridden on subsequent calls to this method.
RecordedParams reclaim_params_;
/// @brief Value of the parameter with which the @c deleteReclaimedLeases
@@ -283,7 +297,8 @@ public:
/// of the class members, it also stops the @c TimerMgr worker thread
/// and removes any registered timers.
CfgExpirationTimersTest()
- : timer_mgr_(TimerMgr::instance()),
+ : io_service_(new IOService()),
+ timer_mgr_(TimerMgr::instance()),
stub_(new LeaseReclamationStub()),
cfg_(true) {
cleanupTimerMgr();
@@ -299,8 +314,8 @@ public:
/// @brief Stop @c TimerMgr worker thread and remove the timers.
void cleanupTimerMgr() const {
- timer_mgr_->stopThread();
timer_mgr_->unregisterTimers();
+ timer_mgr_->setIOService(io_service_);
}
/// @brief Runs timers for specified time.
@@ -310,12 +325,13 @@ public:
///
/// @param timeout_ms Amount of time after which the method returns.
void runTimersWithTimeout(const long timeout_ms) {
- Stopwatch stopwatch;
- while (stopwatch.getTotalMilliseconds() < timeout_ms) {
- // Block for up to one millisecond waiting for the timers'
- // callbacks to be executed.
- IfaceMgr::instancePtr()->receive6(0, 1000);
- }
+ IntervalTimer timer(*io_service_);
+ timer.setup([this]() {
+ io_service_->stop();
+ }, timeout_ms, IntervalTimer::ONE_SHOT);
+
+ io_service_->run();
+ io_service_->get_io_service().reset();
}
/// @brief Setup timers according to the configuration and run them
@@ -328,12 +344,13 @@ public:
stub_.get());
// Run timers.
ASSERT_NO_THROW({
- timer_mgr_->startThread();
runTimersWithTimeout(timeout_ms);
- timer_mgr_->stopThread();
});
}
+ /// @brief Pointer to the IO service used by the tests.
+ IOServicePtr io_service_;
+
/// @brief Pointer to the @c TimerMgr.
TimerMgrPtr timer_mgr_;
@@ -401,7 +418,7 @@ TEST_F(CfgExpirationTimersTest, noLeaseAffinity) {
EXPECT_EQ(0, stub_->delete_calls_count_);
}
-// This test verfies that lease reclamation may be disabled.
+// This test verifies that lease reclamation may be disabled.
TEST_F(CfgExpirationTimersTest, noLeaseReclamation) {
// Disable both timers.
cfg_.setReclaimTimerWaitTime(0);
diff --git a/src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc b/src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc
index b1c51ecdf6..0176eb48c8 100644
--- a/src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,12 +8,14 @@
#include <dhcp/dhcp6.h>
#include <dhcpsrv/cfg_host_operations.h>
#include <dhcpsrv/host.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <iterator>
using namespace isc;
using namespace isc::dhcp;
+using namespace isc::test;
namespace {
@@ -51,6 +53,7 @@ identifierAtPosition(const CfgHostOperations& cfg, const Host::IdentifierType& i
TEST(CfgHostOperationsTest, defaults) {
CfgHostOperations cfg;
EXPECT_TRUE(cfg.getIdentifierTypes().empty());
+ runToElementTest<CfgHostOperations>("[ ]", cfg);
}
// This test verifies that identifier types can be added into an
@@ -76,12 +79,17 @@ TEST(CfgHostOperationsTest, addIdentifier) {
EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_DUID, 1));
EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_CIRCUIT_ID, 2));
+ // Check unparse
+ std::string ids = "[ \"hw-address\", \"duid\", \"circuit-id\" ]";
+ runToElementTest<CfgHostOperations>(ids, cfg);
+
// Let's clear and make sure no identifiers are present.
ASSERT_NO_THROW(cfg.clearIdentifierTypes());
EXPECT_TRUE(cfg.getIdentifierTypes().empty());
+ runToElementTest<CfgHostOperations>("[ ]", cfg);
}
-// This test verfies that the default DHCPv4 configuration is created
+// This test verifies that the default DHCPv4 configuration is created
// as expected.
TEST(CfgHostOperationsTest, createConfig4) {
CfgHostOperationsPtr cfg = CfgHostOperations::createConfig4();
@@ -89,9 +97,10 @@ TEST(CfgHostOperationsTest, createConfig4) {
EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_HWADDR, 0));
EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_DUID, 1));
EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_CIRCUIT_ID, 2));
+ EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_CLIENT_ID, 3));
}
-// This test verfies that the default DHCPv6 configuration is created
+// This test verifies that the default DHCPv6 configuration is created
// as expected.
TEST(CfgHostOperationsTest, createConfig6) {
CfgHostOperationsPtr cfg = CfgHostOperations::createConfig6();
diff --git a/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
index f8529d34b6..73dc6a4e53 100644
--- a/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,7 +9,9 @@
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_hosts_util.h>
#include <dhcpsrv/host.h>
+#include <dhcpsrv/cfgmgr.h>
#include <gtest/gtest.h>
#include <sstream>
#include <set>
@@ -78,7 +80,7 @@ CfgHostsTest::CfgHostsTest() {
const uint32_t addra_template = 0xc0000205; // 192.0.2.5
const uint32_t addrb_template = 0xc00a020a; // 192.10.2.10
- for (int i = 0; i < 50; ++i) {
+ for (unsigned i = 0; i < 50; ++i) {
IOAddress addra(addra_template + i);
addressesa_.push_back(addra);
IOAddress addrb(addrb_template + i);
@@ -102,7 +104,7 @@ TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
CfgHosts cfg;
// Add 25 hosts identified by HW address and 25 hosts identified by
// DUID. They are added to different subnets.
- for (int i = 0; i < 25; ++i) {
+ for (unsigned i = 0; i < 25; ++i) {
cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
"hw-address",
SubnetID(i % 10 + 1), SubnetID(i % 5 + 1),
@@ -151,7 +153,7 @@ TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
TEST_F(CfgHostsTest, getAllRepeatingHosts) {
CfgHosts cfg;
// Add hosts.
- for (int i = 0; i < 25; ++i) {
+ for (unsigned i = 0; i < 25; ++i) {
// Add two hosts, using the same HW address to two distinct subnets.
cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
"hw-address",
@@ -172,7 +174,7 @@ TEST_F(CfgHostsTest, getAllRepeatingHosts) {
}
// Verify that hosts can be retrieved.
- for (int i = 0; i < 25; ++i) {
+ for (unsigned i = 0; i < 25; ++i) {
// Get host by HW address. The DUID is non-null but the reservation
// should be returned for the HW address because there are no
// reservations for the DUIDs from the range of 25 to 49.
@@ -205,7 +207,7 @@ TEST_F(CfgHostsTest, getAllRepeatingHosts) {
TEST_F(CfgHostsTest, getAll4ByAddress) {
CfgHosts cfg;
// Add hosts.
- for (int i = 0; i < 25; ++i) {
+ for (unsigned i = 0; i < 25; ++i) {
// Add host identified by the HW address.
cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
"hw-address",
@@ -268,6 +270,90 @@ TEST_F(CfgHostsTest, get4) {
}
}
+// This test checks that the DHCPv4 reservations can be unparsed
+TEST_F(CfgHostsTest, unparsed4) {
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHosts cfg;
+ CfgHostsList list;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1 + i), SubnetID(13),
+ increase(IOAddress("192.0.2.5"), i))));
+
+ // Add host identified by DUID.
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1 + i), SubnetID(13),
+ increase(IOAddress("192.0.2.100"), i))));
+ }
+
+ using namespace isc::data;
+ ConstElementPtr cfg_unparsed;
+ ASSERT_NO_THROW(cfg_unparsed = cfg.toElement());
+ ASSERT_NO_THROW(list.internalize(cfg_unparsed));
+ for (unsigned i = 0; i < 25; ++i) {
+ ConstElementPtr unparsed = list.get(SubnetID(1 + i));
+ ASSERT_TRUE(unparsed);
+ ASSERT_EQ(Element::list, unparsed->getType());
+ EXPECT_EQ(2, unparsed->size());
+ ASSERT_NE(0, unparsed->size());
+
+ // Check by HW address entries
+ bool checked_hw = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("hw-address")) {
+ continue;
+ }
+ checked_hw = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("duid"));
+ // Check the HW address
+ ConstElementPtr hw = host->get("hw-address");
+ ASSERT_TRUE(hw);
+ ASSERT_EQ(Element::string, hw->getType());
+ EXPECT_EQ(hwaddrs_[i]->toText(false), hw->stringValue());
+ // Check the reservation
+ ConstElementPtr resv = host->get("ip-address");
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("192.0.2.5"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_hw);
+
+ // Check by DUID entries
+ bool checked_duid = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("duid")) {
+ continue;
+ }
+ checked_duid = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("hw-address"));
+ // Check the DUID
+ ConstElementPtr duid = host->get("duid");
+ ASSERT_TRUE(duid);
+ ASSERT_EQ(Element::string, duid->getType());
+ EXPECT_EQ(duids_[i]->toText(), duid->stringValue());
+ // Check the reservation
+ ConstElementPtr resv = host->get("ip-address");
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("192.0.2.100"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_duid);
+ }
+}
+
// This test checks that the reservations can be retrieved for the particular
// host connected to the specific IPv6 subnet (by subnet id).
TEST_F(CfgHostsTest, get6) {
@@ -319,6 +405,108 @@ TEST_F(CfgHostsTest, get6) {
}
}
+// This test checks that the DHCPv6 reservations can be unparsed
+TEST_F(CfgHostsTest, unparse6) {
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHosts cfg;
+ CfgHostsList list;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address.
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(10), SubnetID(1 + i),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+
+ // Add host identified by DUID.
+ host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(10), SubnetID(1 + i),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:2::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ using namespace isc::data;
+ ConstElementPtr cfg_unparsed;
+ ASSERT_NO_THROW(cfg_unparsed = cfg.toElement());
+ ASSERT_NO_THROW(list.internalize(cfg_unparsed));
+ for (unsigned i = 0; i < 25; ++i) {
+ ConstElementPtr unparsed = list.get(SubnetID(1 + i));
+ ASSERT_TRUE(unparsed);
+ ASSERT_EQ(Element::list, unparsed->getType());
+ EXPECT_EQ(2, unparsed->size());
+ ASSERT_NE(0, unparsed->size());
+
+ // Check by HW address entries
+ bool checked_hw = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("hw-address")) {
+ continue;
+ }
+ checked_hw = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("duid"));
+ // Check the HW address
+ ConstElementPtr hw = host->get("hw-address");
+ ASSERT_TRUE(hw);
+ ASSERT_EQ(Element::string, hw->getType());
+ EXPECT_EQ(hwaddrs_[i]->toText(false), hw->stringValue());
+ // Check the reservation
+ ConstElementPtr resvs = host->get("ip-addresses");
+ ASSERT_TRUE(resvs);
+ ASSERT_EQ(Element::list, resvs->getType());
+ EXPECT_EQ(1, resvs->size());
+ ASSERT_GE(1, resvs->size());
+ ConstElementPtr resv = resvs->get(0);
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_hw);
+
+ // Check by DUID entries
+ bool checked_duid = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("duid")) {
+ continue;
+ }
+ checked_duid = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("hw-address"));
+ // Check the DUID
+ ConstElementPtr duid = host->get("duid");
+ ASSERT_TRUE(duid);
+ ASSERT_EQ(Element::string, duid->getType());
+ EXPECT_EQ(duids_[i]->toText(), duid->stringValue());
+ // Check the reservation
+ ConstElementPtr resvs = host->get("ip-addresses");
+ ASSERT_TRUE(resvs);
+ ASSERT_EQ(Element::list, resvs->getType());
+ EXPECT_EQ(1, resvs->size());
+ ASSERT_GE(1, resvs->size());
+ ConstElementPtr resv = resvs->get(0);
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_duid);
+ }
+}
+
// This test checks that the IPv6 reservations can be retrieved for a particular
// (subnet-id, address) tuple.
TEST_F(CfgHostsTest, get6ByAddr) {
@@ -365,7 +553,7 @@ TEST_F(CfgHostsTest, get6MultipleAddrs) {
IOAddress("0.0.0.0")));
// Generate 5 unique addresses for this host.
- for (int j = 0; j < 5; ++j) {
+ for (unsigned j = 0; j < 5; ++j) {
std::stringstream address_stream;
address_stream << "2001:db8:" << i << "::" << j;
host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
diff --git a/src/lib/dhcpsrv/tests/cfg_iface_unittest.cc b/src/lib/dhcpsrv/tests/cfg_iface_unittest.cc
index f544bc241f..efea58beda 100644
--- a/src/lib/dhcpsrv/tests/cfg_iface_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_iface_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,11 +8,13 @@
#include <dhcp/dhcp4.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcpsrv/cfg_iface.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
using namespace isc;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
+using namespace isc::test;
namespace {
@@ -342,7 +344,7 @@ TEST_F(CfgIfaceTest, equality) {
EXPECT_FALSE(cfg1 == cfg2);
EXPECT_TRUE(cfg1 != cfg2);
- // Finally, both are equal as they use wildacard.
+ // Finally, both are equal as they use wildcard.
cfg2.use(AF_INET, "*");
EXPECT_TRUE(cfg1 == cfg2);
EXPECT_FALSE(cfg1 != cfg2);
@@ -358,6 +360,33 @@ TEST_F(CfgIfaceTest, equality) {
EXPECT_FALSE(cfg1 != cfg2);
}
+// This test verifies that it is possible to unparse the interface config.
+TEST_F(CfgIfaceTest, unparse) {
+ CfgIface cfg4;
+
+ // Add things in it
+ EXPECT_NO_THROW(cfg4.use(AF_INET, "*"));
+ EXPECT_NO_THROW(cfg4.use(AF_INET, "eth0"));
+ EXPECT_NO_THROW(cfg4.use(AF_INET, "eth1/192.0.2.3"));
+
+ // Check unparse
+ std::string expected =
+ "{ \"interfaces\": [ \"*\", \"eth0\", \"eth1/192.0.2.3\" ], "
+ "\"re-detect\": false }";
+ runToElementTest<CfgIface>(expected, cfg4);
+
+ // Now check IPv6
+ CfgIface cfg6;
+ EXPECT_NO_THROW(cfg6.use(AF_INET6, "*"));
+ EXPECT_NO_THROW(cfg6.use(AF_INET6, "eth1"));
+ EXPECT_NO_THROW(cfg6.use(AF_INET6, "eth0/2001:db8:1::1"));
+
+ expected =
+ "{ \"interfaces\": [ \"*\", \"eth1\", \"eth0/2001:db8:1::1\" ], "
+ "\"re-detect\": false }";
+ runToElementTest<CfgIface>(expected, cfg6);
+}
+
// This test verifies that it is possible to specify the socket
// type to be used by the DHCPv4 server.
// This test is enabled on LINUX and BSD only, because the
@@ -372,6 +401,13 @@ TEST(CfgIfaceNoStubTest, useSocketType) {
// For datagram sockets, the direct traffic is not supported.
ASSERT_TRUE(!IfaceMgr::instance().isDirectResponseSupported());
+ // Check unparse
+ std::string expected = "{\n"
+ " \"interfaces\": [ ],\n"
+ " \"dhcp-socket-type\": \"udp\",\n"
+ " \"re-detect\": false }";
+ runToElementTest<CfgIface>(expected, cfg);
+
// Select raw sockets.
ASSERT_NO_THROW(cfg.useSocketType(AF_INET, "raw"));
EXPECT_EQ("raw", cfg.socketTypeToText());
diff --git a/src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc b/src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc
index c54158db36..b02c791d63 100644
--- a/src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc
@@ -1,18 +1,23 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#include <config.h>
+
#include <dhcpsrv/cfg_mac_source.h>
#include <dhcp/hwaddr.h>
#include <exceptions/exceptions.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
+#include <string>
namespace {
using namespace isc;
using namespace isc::dhcp;
+using namespace isc::test;
// Checks whether CfgMACSource::MACSourceFromText is working correctly.
// Technically, this is a Pkt, not Pkt6 test, but since there is no separate
@@ -44,4 +49,36 @@ TEST(CfgMACSourceTest, MACSourceFromText) {
CfgMACSource::MACSourceFromText("docsis-modem"));
}
+// Checks whether the opposite operation is working correctly.
+TEST(CfgMACSourceTest, unparse) {
+ CfgMACSource cfg;
+ // any was added by the constructor
+ cfg.add(HWAddr::HWADDR_SOURCE_RAW);
+ cfg.add(HWAddr::HWADDR_SOURCE_DUID);
+ cfg.add(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL);
+ cfg.add(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION);
+ cfg.add(HWAddr::HWADDR_SOURCE_REMOTE_ID);
+ cfg.add(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID);
+ cfg.add(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS);
+ cfg.add(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM);
+
+ // Unparse
+ std::string expected = "["
+ "\"any\","
+ "\"raw\","
+ "\"duid\","
+ "\"ipv6-link-local\","
+ "\"client-link-addr-option\","
+ "\"remote-id\","
+ "\"subscriber-id\","
+ "\"docsis-cmts\","
+ "\"docsis-modem\""
+ "]";
+ runToElementTest<CfgMACSource>(expected, cfg);
+
+ // Add an unknown type
+ cfg.add(0x12345678);
+ ASSERT_THROW(cfg.toElement(), ToElementError);
+}
+
};
diff --git a/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc
index cb77858781..e0c8987c22 100644
--- a/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
#include <dhcp/dhcp6.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/cfg_option_def.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
using namespace isc;
@@ -245,4 +246,59 @@ TEST(CfgOptionDefTest, addNegative) {
EXPECT_THROW(cfg.add(def, "isc"), DuplicateOptionDefinition);
}
+// This test verifies that the function that unparses configuration
+// works as expected.
+TEST(CfgOptionDefTest, unparse) {
+ CfgOptionDef cfg;
+
+ // Add some options.
+ cfg.add(OptionDefinitionPtr(new
+ OptionDefinition("option-foo", 5, "uint16")), "isc");
+ cfg.add(OptionDefinitionPtr(new
+ OptionDefinition("option-bar", 5, "uint16", true)), "dns");
+ cfg.add(OptionDefinitionPtr(new
+ OptionDefinition("option-baz", 6, "uint16", "dns")), "isc");
+ OptionDefinitionPtr rec(new OptionDefinition("option-rec", 6, "record"));
+ rec->addRecordField("uint16");
+ rec->addRecordField("uint16");
+ cfg.add(rec, "dns");
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"name\": \"option-bar\",\n"
+ " \"code\": 5,\n"
+ " \"type\": \"uint16\",\n"
+ " \"array\": true,\n"
+ " \"record-types\": \"\",\n"
+ " \"encapsulate\": \"\",\n"
+ " \"space\": \"dns\"\n"
+ "},{\n"
+ " \"name\": \"option-rec\",\n"
+ " \"code\": 6,\n"
+ " \"type\": \"record\",\n"
+ " \"array\": false,\n"
+ " \"record-types\": \"uint16, uint16\",\n"
+ " \"encapsulate\": \"\",\n"
+ " \"space\": \"dns\"\n"
+ "},{\n"
+ " \"name\": \"option-foo\",\n"
+ " \"code\": 5,\n"
+ " \"type\": \"uint16\",\n"
+ " \"array\": false,\n"
+ " \"record-types\": \"\",\n"
+ " \"encapsulate\": \"\",\n"
+ " \"space\": \"isc\"\n"
+ "},{\n"
+ " \"name\": \"option-baz\",\n"
+ " \"code\": 6,\n"
+ " \"type\": \"uint16\",\n"
+ " \"array\": false,\n"
+ " \"record-types\": \"\",\n"
+ " \"encapsulate\": \"dns\",\n"
+ " \"space\": \"isc\"\n"
+ "}]\n";
+ isc::test::runToElementTest<CfgOptionDef>(expected, cfg);
+}
+
}
diff --git a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc
index 0eb015e441..99d0c7d656 100644
--- a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
#include <dhcp/option_int.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/cfg_option.h>
+#include <testutils/test_to_element.h>
#include <boost/foreach.hpp>
#include <boost/pointer_cast.hpp>
#include <gtest/gtest.h>
@@ -448,11 +449,9 @@ TEST_F(CfgOptionTest, addNonUniqueOptions) {
// Look for the codes 100-109.
for (uint16_t code = 100; code < 110; ++ code) {
// For each code we should get two instances of options->
- std::pair<OptionContainerTypeIndex::const_iterator,
- OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(code);
+ OptionContainerTypeRange range = idx.equal_range(code);
// Distance between iterators indicates how many options
- // have been retured for the particular code.
+ // have been returned for the particular code.
ASSERT_EQ(2, distance(range.first, range.second));
// Check that returned options actually have the expected option code.
for (OptionContainerTypeIndex::const_iterator option_desc = range.first;
@@ -464,9 +463,7 @@ TEST_F(CfgOptionTest, addNonUniqueOptions) {
// Let's try to find some non-exiting option.
const uint16_t non_existing_code = 150;
- std::pair<OptionContainerTypeIndex::const_iterator,
- OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(non_existing_code);
+ OptionContainerTypeRange range = idx.equal_range(non_existing_code);
// Empty set is expected.
EXPECT_EQ(0, distance(range.first, range.second));
}
@@ -500,17 +497,13 @@ TEST(Subnet6Test, addPersistentOption) {
OptionContainerPersistIndex& idx = options->get<2>();
// Get all persistent options->
- std::pair<OptionContainerPersistIndex::const_iterator,
- OptionContainerPersistIndex::const_iterator> range_persistent =
- idx.equal_range(true);
- // 3 out of 10 options have been flagged persistent.
+ OptionContainerPersistRange range_persistent = idx.equal_range(true);
+ // 7 out of 10 options have been flagged persistent.
ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
// Get all non-persistent options->
- std::pair<OptionContainerPersistIndex::const_iterator,
- OptionContainerPersistIndex::const_iterator> range_non_persistent =
- idx.equal_range(false);
- // 7 out of 10 options have been flagged persistent.
+ OptionContainerPersistRange range_non_persistent = idx.equal_range(false);
+ // 3 out of 10 options have been flagged not persistent.
ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
}
@@ -598,5 +591,50 @@ TEST_F(CfgOptionTest, getVendorIdsSpaceNames) {
}
}
+// This test verifies that the unparse function returns what is expected.
+TEST_F(CfgOptionTest, unparse) {
+ CfgOption cfg;
+
+ // Add some options.
+ OptionPtr opt1(new Option(Option::V6, 100, OptionBuffer(4, 0x12)));
+ cfg.add(opt1, false, "dns");
+ OptionPtr opt2(new Option(Option::V6, 101, OptionBuffer(4, 12)));
+ OptionDescriptor desc2(opt2, false, "12, 12, 12, 12");
+ cfg.add(desc2, "dns");
+ OptionPtr opt3(new Option(Option::V6, D6O_STATUS_CODE, OptionBuffer(2, 0)));
+ cfg.add(opt3, false, DHCP6_OPTION_SPACE);
+ OptionPtr opt4(new Option(Option::V6, 100, OptionBuffer(4, 0x21)));
+ cfg.add(opt4, true, "vendor-1234");
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"code\": 100,\n"
+ " \"space\": \"dns\",\n"
+ " \"csv-format\": false,\n"
+ " \"data\": \"12121212\",\n"
+ " \"always-send\": false\n"
+ "},{\n"
+ " \"code\": 101,\n"
+ " \"space\": \"dns\",\n"
+ " \"csv-format\": true,\n"
+ " \"data\": \"12, 12, 12, 12\",\n"
+ " \"always-send\": false\n"
+ "},{\n"
+ " \"code\": 13,\n"
+ " \"name\": \"status-code\",\n"
+ " \"space\": \"dhcp6\",\n"
+ " \"csv-format\": false,\n"
+ " \"data\": \"0000\",\n"
+ " \"always-send\": false\n"
+ "},{\n"
+ " \"code\": 100,\n"
+ " \"space\": \"vendor-1234\",\n"
+ " \"csv-format\": false,\n"
+ " \"data\": \"21212121\",\n"
+ " \"always-send\": true\n"
+ "}]\n";
+ isc::test::runToElementTest<CfgOption>(expected, cfg);
+}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc b/src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc
index f82cbf28f3..de2d5bb754 100644
--- a/src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
#include <config.h>
#include <dhcp/dhcp6.h>
#include <dhcpsrv/cfg_rsoo.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
@@ -64,7 +65,7 @@ TEST(CfgRSOOTest, enableAndClear) {
}
}
-// This test verfies that the same option may be specified
+// This test verifies that the same option may be specified
// multiple times and that the code doesn't fail.
TEST(CfgRSOOTest, enableTwice) {
CfgRSOO rsoo;
@@ -88,4 +89,12 @@ TEST(CfgRSOOTest, enableTwice) {
ASSERT_FALSE(rsoo.enabled(88));
}
+// This test verifies that the unparse function returns what is expected.
+TEST(CfgRSOOTest, unparse) {
+ CfgRSOO rsoo;
+ // option codes are put in strings
+ isc::test::runToElementTest<CfgRSOO>("[ \"65\" ]", rsoo);
+ // isc::test::runToElementTest<CfgRSOO>("[ 65 ]", rsoo);
+}
+
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
index f913ab142b..696c477f33 100644
--- a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,6 +11,7 @@
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_id.h>
#include <dhcpsrv/subnet_selector.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
#include <vector>
@@ -18,9 +19,89 @@ using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
+using namespace isc::test;
namespace {
+// This test verifies that specific subnet can be retrieved by specifying
+// subnet identifier or subnet prefix.
+TEST(CfgSubnets4Test, getSpecificSubnet) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(5)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(8)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(10)));
+
+ // Store the subnets in a vector to make it possible to loop over
+ // all configured subnets.
+ std::vector<Subnet4Ptr> subnets;
+ subnets.push_back(subnet1);
+ subnets.push_back(subnet2);
+ subnets.push_back(subnet3);
+
+ // Add all subnets to the configuration.
+ for (auto subnet = subnets.cbegin(); subnet != subnets.cend(); ++subnet) {
+ ASSERT_NO_THROW(cfg.add(*subnet)) << "failed to add subnet with id: "
+ << (*subnet)->getID();
+ }
+
+ // Iterate over all subnets and make sure they can be retrieved by
+ // subnet identifier.
+ for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+ ConstSubnet4Ptr subnet_returned = cfg.getBySubnetId((*subnet)->getID());
+ ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+ << (*subnet)->getID();
+ EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+ EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+ }
+
+ // Repeat the previous test, but this time retrieve subnets by their
+ // prefixes.
+ for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+ ConstSubnet4Ptr subnet_returned = cfg.getByPrefix((*subnet)->toText());
+ ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+ << (*subnet)->getID();
+ EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+ EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+ }
+
+ // Make sure that null pointers are returned for non-existing subnets.
+ EXPECT_FALSE(cfg.getBySubnetId(SubnetID(123)));
+ EXPECT_FALSE(cfg.getByPrefix("10.20.30.0/29"));
+}
+
+// This test verifies that a single subnet can be removed from the configuration.
+TEST(CfgSubnets4Test, deleteSubnet) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(5)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.3.0"),
+ 26, 1, 2, 3, SubnetID(8)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.4.0"),
+ 26, 1, 2, 3, SubnetID(10)));
+
+ ASSERT_NO_THROW(cfg.add(subnet1));
+ ASSERT_NO_THROW(cfg.add(subnet2));
+ ASSERT_NO_THROW(cfg.add(subnet3));
+
+ // There should be three subnets.
+ ASSERT_EQ(3, cfg.getAll()->size());
+ // We're going to remove the subnet #2. Let's make sure it exists before
+ // we remove it.
+ ASSERT_TRUE(cfg.getByPrefix("192.0.3.0/26"));
+
+ // Remove the subnet and make sure it is gone.
+ ASSERT_NO_THROW(cfg.del(subnet2));
+ ASSERT_EQ(2, cfg.getAll()->size());
+ EXPECT_FALSE(cfg.getByPrefix("192.0.3.0/26"));
+}
+
// This test verifies that it is possible to retrieve a subnet using an
// IP address.
TEST(CfgSubnets4Test, selectSubnetByCiaddr) {
@@ -438,5 +519,137 @@ TEST(CfgSubnets4Test, 4o6subnetMatchByInterfaceName) {
EXPECT_EQ(subnet2, cfg.selectSubnet4o6(selector));
}
+// This test check if IPv4 subnets can be unparsed in a predictable way,
+TEST(CfgSubnets4Test, unparseSubnet) {
+ CfgSubnets4 cfg;
+
+ // Add some subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 123));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3, 124));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 125));
+ subnet1->allowClientClass("foo");
+ subnet2->setIface("lo");
+ subnet2->setRelayInfo(IOAddress("10.0.0.1"));
+ subnet3->setIface("eth1");
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"id\": 123,\n"
+ " \"subnet\": \"192.0.2.0/26\",\n"
+ " \"relay\": { \"ip-address\": \"0.0.0.0\" },\n"
+ " \"match-client-id\": true,\n"
+ " \"next-server\": \"0.0.0.0\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"valid-lifetime\": 3,\n"
+ " \"client-class\": \"foo\",\n"
+ " \"4o6-interface\": \"\",\n"
+ " \"4o6-interface-id\": \"\",\n"
+ " \"4o6-subnet\": \"\",\n"
+ " \"reservation-mode\": \"all\",\n"
+ " \"option-data\": [ ],\n"
+ " \"pools\": [ ]\n"
+ "},{\n"
+ " \"id\": 124,\n"
+ " \"subnet\": \"192.0.2.64/26\",\n"
+ " \"relay\": { \"ip-address\": \"10.0.0.1\" },\n"
+ " \"interface\": \"lo\",\n"
+ " \"match-client-id\": true,\n"
+ " \"next-server\": \"0.0.0.0\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"valid-lifetime\": 3,\n"
+ " \"4o6-interface\": \"\",\n"
+ " \"4o6-interface-id\": \"\",\n"
+ " \"4o6-subnet\": \"\",\n"
+ " \"reservation-mode\": \"all\",\n"
+ " \"option-data\": [ ],\n"
+ " \"pools\": [ ]\n"
+ "},{\n"
+ " \"id\": 125,\n"
+ " \"subnet\": \"192.0.2.128/26\",\n"
+ " \"relay\": { \"ip-address\": \"0.0.0.0\" },\n"
+ " \"interface\": \"eth1\",\n"
+ " \"match-client-id\": true,\n"
+ " \"next-server\": \"0.0.0.0\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"valid-lifetime\": 3,\n"
+ " \"4o6-interface\": \"\",\n"
+ " \"4o6-interface-id\": \"\",\n"
+ " \"4o6-subnet\": \"\",\n"
+ " \"reservation-mode\": \"all\",\n"
+ " \"option-data\": [ ],\n"
+ " \"pools\": [ ]\n"
+ "} ]\n";
+ runToElementTest<CfgSubnets4>(expected, cfg);
+}
+
+// This test check if IPv4 pools can be unparsed in a predictable way,
+TEST(CfgSubnets4Test, unparsePool) {
+ CfgSubnets4 cfg;
+
+ // Add a subnet with pools
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 123));
+ Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.1"), IOAddress("192.0.2.10")));
+ Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.64"), 26));
+
+ subnet->addPool(pool1);
+ subnet->addPool(pool2);
+ cfg.add(subnet);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"id\": 123,\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"relay\": { \"ip-address\": \"0.0.0.0\" },\n"
+ " \"match-client-id\": true,\n"
+ " \"next-server\": \"0.0.0.0\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"valid-lifetime\": 3,\n"
+ " \"4o6-interface\": \"\",\n"
+ " \"4o6-interface-id\": \"\",\n"
+ " \"4o6-subnet\": \"\",\n"
+ " \"reservation-mode\": \"all\",\n"
+ " \"option-data\": [],\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"option-data\": [ ],\n"
+ " \"pool\": \"192.0.2.1-192.0.2.10\"\n"
+ " },{\n"
+ " \"option-data\": [ ],\n"
+ " \"pool\": \"192.0.2.64/26\"\n"
+ " }\n"
+ " ]\n"
+ "} ]\n";
+ runToElementTest<CfgSubnets4>(expected, cfg);
+}
+
+// This test verifies that it is possible to retrieve a subnet using subnet-id.
+TEST(CfgSubnets4Test, getSubnet) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 100));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3, 200));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 300));
+
+ // Add one subnet and make sure it is returned.
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ EXPECT_EQ(subnet1, cfg.getSubnet(100));
+ EXPECT_EQ(subnet2, cfg.getSubnet(200));
+ EXPECT_EQ(subnet3, cfg.getSubnet(300));
+ EXPECT_EQ(Subnet4Ptr(), cfg.getSubnet(400)); // no such subnet
+}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
index e42260386a..575a0b8e90 100644
--- a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12,12 +12,14 @@
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_id.h>
#include <dhcpsrv/subnet_selector.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
#include <string>
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using namespace isc::test;
namespace {
@@ -30,6 +32,82 @@ generateInterfaceId(const std::string& text) {
return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
}
+// This test verifies that specific subnet can be retrieved by specifying
+// subnet identifier or subnet prefix.
+TEST(CfgSubnets6Test, getSpecificSubnet) {
+ CfgSubnets6 cfg;
+
+ // Create 3 subnets.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4,
+ SubnetID(5)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4,
+ SubnetID(8)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4,
+ SubnetID(10)));
+
+ // Store the subnets in a vector to make it possible to loop over
+ // all configured subnets.
+ std::vector<Subnet6Ptr> subnets;
+ subnets.push_back(subnet1);
+ subnets.push_back(subnet2);
+ subnets.push_back(subnet3);
+
+ // Add all subnets to the configuration.
+ for (auto subnet = subnets.cbegin(); subnet != subnets.cend(); ++subnet) {
+ ASSERT_NO_THROW(cfg.add(*subnet)) << "failed to add subnet with id: "
+ << (*subnet)->getID();
+ }
+
+ // Iterate over all subnets and make sure they can be retrieved by
+ // subnet identifier.
+ for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+ ConstSubnet6Ptr subnet_returned = cfg.getBySubnetId((*subnet)->getID());
+ ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+ << (*subnet)->getID();
+ EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+ EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+ }
+
+ // Repeat the previous test, but this time retrieve subnets by their
+ // prefixes.
+ for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+ ConstSubnet6Ptr subnet_returned = cfg.getByPrefix((*subnet)->toText());
+ ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+ << (*subnet)->getID();
+ EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+ EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+ }
+
+ // Make sure that null pointers are returned for non-existing subnets.
+ EXPECT_FALSE(cfg.getBySubnetId(SubnetID(123)));
+ EXPECT_FALSE(cfg.getByPrefix("3000::/16"));
+}
+
+// This test verifies that a single subnet can be removed from the configuration.
+TEST(CfgSubnets6Test, deleteSubnet) {
+ CfgSubnets6 cfg;
+
+ // Create 3 subnets.
+ 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));
+
+ ASSERT_NO_THROW(cfg.add(subnet1));
+ ASSERT_NO_THROW(cfg.add(subnet2));
+ ASSERT_NO_THROW(cfg.add(subnet3));
+
+ // There should be three subnets.
+ ASSERT_EQ(3, cfg.getAll()->size());
+ // We're going to remove the subnet #2. Let's make sure it exists before
+ // we remove it.
+ ASSERT_TRUE(cfg.getByPrefix("2001:db8:2::/48"));
+
+ // Remove the subnet and make sure it is gone.
+ ASSERT_NO_THROW(cfg.del(subnet2));
+ ASSERT_EQ(2, cfg.getAll()->size());
+ EXPECT_FALSE(cfg.getByPrefix("2001:db8:2::/48"));
+}
+
// This test checks that the subnet can be selected using a relay agent's
// link address.
TEST(CfgSubnets6Test, selectSubnetByRelayAddress) {
@@ -240,9 +318,9 @@ TEST(CfgSubnets6Test, selectSubnetByRelayAddressAndClassify) {
EXPECT_FALSE(cfg.selectSubnet(selector));
}
-// Test that client classes are considered when the subnet is selcted by the
+// Test that client classes are considered when the subnet is selected by the
// interface name.
-TEST(CfgSubnets6Test, selectSubnetByInterfaceNameAndClaassify) {
+TEST(CfgSubnets6Test, selectSubnetByInterfaceNameAndClassify) {
CfgSubnets6 cfg;
Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
@@ -342,4 +420,187 @@ TEST(CfgSubnets6Test, duplication) {
EXPECT_THROW(cfg.add(subnet3), isc::dhcp::DuplicateSubnetID);
}
+// This test check if IPv6 subnets can be unparsed in a predictable way,
+TEST(CfgSubnets6Test, unparseSubnet) {
+ CfgSubnets6 cfg;
+
+ // Add some subnets.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, 123));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"),
+ 48, 1, 2, 3, 4, 124));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"),
+ 48, 1, 2, 3, 4, 125));
+
+ OptionPtr ifaceid = generateInterfaceId("relay.eth0");
+ subnet1->setInterfaceId(ifaceid);
+ subnet1->allowClientClass("foo");
+ subnet2->setIface("lo");
+ subnet2->setRelayInfo(IOAddress("2001:db8:ff::2"));
+ subnet3->setIface("eth1");
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"id\": 123,\n"
+ " \"subnet\": \"2001:db8:1::/48\",\n"
+ " \"relay\": { \"ip-address\": \"::\" },\n"
+ " \"interface-id\": \"relay.eth0\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"preferred-lifetime\": 3,\n"
+ " \"valid-lifetime\": 4,\n"
+ " \"rapid-commit\": false,\n"
+ " \"reservation-mode\": \"all\",\n"
+ " \"client-class\": \"foo\",\n"
+ " \"pools\": [ ],\n"
+ " \"pd-pools\": [ ],\n"
+ " \"option-data\": [ ]\n"
+ "},{\n"
+ " \"id\": 124,\n"
+ " \"subnet\": \"2001:db8:2::/48\",\n"
+ " \"relay\": { \"ip-address\": \"2001:db8:ff::2\" },\n"
+ " \"interface\": \"lo\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"preferred-lifetime\": 3,\n"
+ " \"valid-lifetime\": 4,\n"
+ " \"rapid-commit\": false,\n"
+ " \"reservation-mode\": \"all\",\n"
+ " \"pools\": [ ],\n"
+ " \"pd-pools\": [ ],\n"
+ " \"option-data\": [ ]\n"
+ "},{\n"
+ " \"id\": 125,\n"
+ " \"subnet\": \"2001:db8:3::/48\",\n"
+ " \"relay\": { \"ip-address\": \"::\" },\n"
+ " \"interface\": \"eth1\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"preferred-lifetime\": 3,\n"
+ " \"valid-lifetime\": 4,\n"
+ " \"rapid-commit\": false,\n"
+ " \"reservation-mode\": \"all\",\n"
+ " \"pools\": [ ],\n"
+ " \"pd-pools\": [ ],\n"
+ " \"option-data\": [ ]\n"
+ "} ]\n";
+ runToElementTest<CfgSubnets6>(expected, cfg);
+}
+
+// This test check if IPv6 pools can be unparsed in a predictable way,
+TEST(CfgSubnets6Test, unparsePool) {
+ CfgSubnets6 cfg;
+
+ // Add a subnet with pools
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, 123));
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::100"),
+ IOAddress("2001:db8:1::199")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64));
+
+ subnet->addPool(pool1);
+ subnet->addPool(pool2);
+ cfg.add(subnet);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"id\": 123,\n"
+ " \"subnet\": \"2001:db8:1::/48\",\n"
+ " \"relay\": { \"ip-address\": \"::\" },\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"preferred-lifetime\": 3,\n"
+ " \"valid-lifetime\": 4,\n"
+ " \"rapid-commit\": false,\n"
+ " \"reservation-mode\": \"all\",\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"2001:db8:1::100-2001:db8:1::199\",\n"
+ " \"option-data\": [ ]\n"
+ " },{\n"
+ " \"pool\": \"2001:db8:1:1::/64\",\n"
+ " \"option-data\": [ ]\n"
+ " }\n"
+ " ],\n"
+ " \"pd-pools\": [ ],\n"
+ " \"option-data\": [ ]\n"
+ "} ]\n";
+ runToElementTest<CfgSubnets6>(expected, cfg);
+}
+
+// This test check if IPv6 prefix delegation pools can be unparsed
+// in a predictable way,
+TEST(CfgSubnets6Test, unparsePdPool) {
+ CfgSubnets6 cfg;
+
+ // Add a subnet with pd-pools
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, 123));
+ Pool6Ptr pdpool1(new Pool6(Lease::TYPE_PD,
+ IOAddress("2001:db8:2::"), 48, 64));
+ Pool6Ptr pdpool2(new Pool6(IOAddress("2001:db8:3::"), 48, 56,
+ IOAddress("2001:db8:3::"), 64));
+
+ subnet->addPool(pdpool1);
+ subnet->addPool(pdpool2);
+ cfg.add(subnet);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"id\": 123,\n"
+ " \"subnet\": \"2001:db8:1::/48\",\n"
+ " \"relay\": { \"ip-address\": \"::\" },\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"preferred-lifetime\": 3,\n"
+ " \"valid-lifetime\": 4,\n"
+ " \"rapid-commit\": false,\n"
+ " \"reservation-mode\": \"all\",\n"
+ " \"pools\": [ ],\n"
+ " \"pd-pools\": [\n"
+ " {\n"
+ " \"prefix\": \"2001:db8:2::\",\n"
+ " \"prefix-len\": 48,\n"
+ " \"delegated-len\": 64,\n"
+ " \"option-data\": [ ]\n"
+ " },{\n"
+ " \"prefix\": \"2001:db8:3::\",\n"
+ " \"prefix-len\": 48,\n"
+ " \"delegated-len\": 56,\n"
+ " \"excluded-prefix\": \"2001:db8:3::\",\n"
+ " \"excluded-prefix-len\": 64,\n"
+ " \"option-data\": [ ]\n"
+ " }\n"
+ " ],\n"
+ " \"option-data\": [ ]\n"
+ "} ]\n";
+ runToElementTest<CfgSubnets6>(expected, cfg);
+}
+
+// This test verifies that it is possible to retrieve a subnet using subnet-id.
+TEST(CfgSubnets6Test, getSubnet) {
+ CfgSubnets6 cfg;
+
+ // Let's configure 3 subnets
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, 100));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4, 200));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4, 300));
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ EXPECT_EQ(subnet1, cfg.getSubnet(100));
+ EXPECT_EQ(subnet2, cfg.getSubnet(200));
+ EXPECT_EQ(subnet3, cfg.getSubnet(300));
+ EXPECT_EQ(Subnet6Ptr(), cfg.getSubnet(400)); // no such subnet
+}
+
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index a6de68d7dd..e35e7ad783 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -280,6 +280,7 @@ public:
void clear() {
CfgMgr::instance().setVerbose(false);
+ CfgMgr::instance().setFamily(AF_INET);
CfgMgr::instance().clear();
LeaseMgrFactory::destroy();
}
@@ -317,89 +318,6 @@ TEST_F(CfgMgrTest, configuration) {
EXPECT_TRUE(configuration->getLoggingInfo().empty());
}
-// This test verifies that new DHCPv4 option spaces can be added to
-// the configuration manager and that duplicated option space is
-// rejected.
-TEST_F(CfgMgrTest, optionSpace4) {
- CfgMgr& cfg_mgr = CfgMgr::instance();
-
- // Create some option spaces.
- OptionSpacePtr space1(new OptionSpace("isc", false));
- OptionSpacePtr space2(new OptionSpace("xyz", true));
-
- // Add option spaces with different names and expect they
- // are accepted.
- ASSERT_NO_THROW(cfg_mgr.addOptionSpace4(space1));
- ASSERT_NO_THROW(cfg_mgr.addOptionSpace4(space2));
-
- // Validate that the option spaces have been added correctly.
- const OptionSpaceCollection& spaces = cfg_mgr.getOptionSpaces4();
-
- ASSERT_EQ(2, spaces.size());
- EXPECT_FALSE(spaces.find("isc") == spaces.end());
- EXPECT_FALSE(spaces.find("xyz") == spaces.end());
-
- // Create another option space with the name that duplicates
- // the existing option space.
- OptionSpacePtr space3(new OptionSpace("isc", true));
- // Expect that the duplicate option space is rejected.
- ASSERT_THROW(
- cfg_mgr.addOptionSpace4(space3), isc::dhcp::InvalidOptionSpace
- );
-
- /// @todo decode if a duplicate vendor space is allowed.
-}
-
-// This test verifies that new DHCPv6 option spaces can be added to
-// the configuration manager and that duplicated option space is
-// rejected.
-TEST_F(CfgMgrTest, optionSpace6) {
- CfgMgr& cfg_mgr = CfgMgr::instance();
-
- // Create some option spaces.
- OptionSpacePtr space1(new OptionSpace("isc", false));
- OptionSpacePtr space2(new OptionSpace("xyz", true));
-
- // Add option spaces with different names and expect they
- // are accepted.
- ASSERT_NO_THROW(cfg_mgr.addOptionSpace6(space1));
- ASSERT_NO_THROW(cfg_mgr.addOptionSpace6(space2));
-
- // Validate that the option spaces have been added correctly.
- const OptionSpaceCollection& spaces = cfg_mgr.getOptionSpaces6();
-
- ASSERT_EQ(2, spaces.size());
- EXPECT_FALSE(spaces.find("isc") == spaces.end());
- EXPECT_FALSE(spaces.find("xyz") == spaces.end());
-
- // Create another option space with the name that duplicates
- // the existing option space.
- OptionSpacePtr space3(new OptionSpace("isc", true));
- // Expect that the duplicate option space is rejected.
- ASSERT_THROW(
- cfg_mgr.addOptionSpace6(space3), isc::dhcp::InvalidOptionSpace
- );
-
- /// @todo decide if a duplicate vendor space is allowed.
-}
-
-// This test verifies that RFC6842 (echo client-id) compatibility may be
-// configured.
-TEST_F(CfgMgrTest, echoClientId) {
- CfgMgr& cfg_mgr = CfgMgr::instance();
-
- // Check that the default is true
- EXPECT_TRUE(cfg_mgr.echoClientId());
-
- // Check that it can be modified to false
- cfg_mgr.echoClientId(false);
- EXPECT_FALSE(cfg_mgr.echoClientId());
-
- // Check that the default value can be restored
- cfg_mgr.echoClientId(true);
- EXPECT_TRUE(cfg_mgr.echoClientId());
-}
-
// This test checks the D2ClientMgr wrapper methods.
TEST_F(CfgMgrTest, d2ClientConfig) {
// After CfgMgr construction, D2ClientMgr member should be initialized
@@ -588,6 +506,18 @@ TEST_F(CfgMgrTest, verbosity) {
EXPECT_FALSE(CfgMgr::instance().isVerbose());
}
+// This test verifies that the address family can be set and obtained
+// from the configuration manager.
+TEST_F(CfgMgrTest, family) {
+ ASSERT_EQ(AF_INET, CfgMgr::instance().getFamily());
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ ASSERT_EQ(AF_INET6, CfgMgr::instance().getFamily());
+
+ CfgMgr::instance().setFamily(AF_INET);
+ EXPECT_EQ(AF_INET, CfgMgr::instance().getFamily());
+}
+
// This test verifies that once the configuration is committed, statistics
// are updated appropriately.
TEST_F(CfgMgrTest, commitStats4) {
diff --git a/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
index bdfcae94fe..648b782927 100644
--- a/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12,6 +12,7 @@
#include <dhcp/option_string.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <asiolink/io_address.h>
#include <eval/evaluate.h>
#include <gtest/gtest.h>
@@ -39,47 +40,43 @@ protected:
/// produces an Expression which can be evaluated against a v4 or v6
/// packet.
///
- /// @param universe V4 or V6.
+ /// @param family AF_INET or AF_INET6
/// @param expression Textual representation of the expression to be
/// evaluated.
/// @param option_string String data to be placed in the hostname
/// option, being placed in the packet used for evaluation.
/// @tparam Type of the packet: @c Pkt4 or @c Pkt6.
template<typename PktType>
- void testValidExpression(const Option::Universe& universe,
+ void testValidExpression(uint16_t family,
const std::string& expression,
const std::string& option_string) {
- ParserContextPtr context(new ParserContext(universe));
- ExpressionParserPtr parser;
ExpressionPtr parsed_expr;
+ ExpressionParser parser;
// Turn config into elements. This may emit exceptions.
ElementPtr config_element = Element::fromJSON(expression);
- // Create the parser.
- ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr,
- context)));
- // Expression should parse and commit.
- ASSERT_NO_THROW(parser->build(config_element));
- ASSERT_NO_THROW(parser->commit());
+ // Expression should parse.
+ ASSERT_NO_THROW(parser.parse(parsed_expr, config_element, family));
// Parsed expression should exist.
ASSERT_TRUE(parsed_expr);
// Build a packet that will fail evaluation.
- boost::shared_ptr<PktType> pkt(new PktType(universe == Option::V4 ?
+ boost::shared_ptr<PktType> pkt(new PktType(family == AF_INET ?
DHCPDISCOVER : DHCPV6_SOLICIT,
123));
- EXPECT_FALSE(evaluate(*parsed_expr, *pkt));
+ EXPECT_FALSE(evaluateBool(*parsed_expr, *pkt));
// Now add the option so it will pass. Use a standard option carrying a
// single string value, i.e. hostname for DHCPv4 and bootfile url for
// DHCPv6.
- OptionPtr opt(new OptionString(universe, universe == Option::V4 ?
+ Option::Universe universe(family == AF_INET ? Option::V4 : Option::V6);
+ OptionPtr opt(new OptionString(universe, family == AF_INET ?
DHO_HOST_NAME : D6O_BOOTFILE_URL,
option_string));
pkt->addOption(opt);
- EXPECT_TRUE(evaluate(*parsed_expr, *pkt));
+ EXPECT_TRUE(evaluateBool(*parsed_expr, *pkt));
}
};
@@ -89,29 +86,27 @@ protected:
/// @brief Convenience method for parsing a configuration
///
- /// Attempt to parse a given client class defintion.
+ /// Attempt to parse a given client class definition.
///
/// @param config - JSON string containing the client class configuration
/// to parse.
- /// @param universe - the universe in which the parsing context should
+ /// @param family - the address family in which the parsing should
/// occur.
/// @return Returns a pointer to class instance created, or NULL if
/// for some unforeseen reason it wasn't created in the local dictionary
/// @throw indirectly, exceptions convertring the JSON text to elements,
/// or by the parsing itself are not caught
ClientClassDefPtr parseClientClassDef(const std::string& config,
- Option::Universe universe) {
- // Create local dicitonary to which the parser add the class.
+ uint16_t family) {
+ // Create local dictionary to which the parser add the class.
ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
- // Create the "global" context for the parser.
- ParserContextPtr context(new ParserContext(universe));
// Turn config into elements. This may emit exceptions.
ElementPtr config_element = Element::fromJSON(config);
// Parse the configuration. This may emit exceptions.
- ClientClassDefParser parser("", dictionary, context);
- parser.build(config_element);
+ ClientClassDefParser parser;
+ parser.parse(dictionary, config_element, family);
// If we didn't throw, then return the first and only class
ClientClassDefMapPtr classes = dictionary->getClasses();
@@ -132,46 +127,38 @@ protected:
/// @brief Convenience method for parsing a list of client class
/// definitions.
///
- /// Attempt to parse a given list of client class defintions into a
+ /// Attempt to parse a given list of client class definitions into a
/// ClientClassDictionary.
///
/// @param config - JSON string containing the list of definitions to parse.
- /// @param universe - the universe in which the parsing context should
+ /// @param family - the address family in which the parsing should
/// occur.
/// @return Returns a pointer to class dictionary created
/// @throw indirectly, execptions convertring the JSON text to elements,
/// or by the parsing itself are not caught
ClientClassDictionaryPtr parseClientClassDefList(const std::string& config,
- Option::Universe universe)
+ uint16_t family)
{
- // Create the "global" context for the parser.
- ParserContextPtr context(new ParserContext(universe));
-
// Turn config into elements. This may emit exceptions.
ElementPtr config_element = Element::fromJSON(config);
// Parse the configuration. This may emit exceptions.
- ClientClassDefListParser parser("", context);
- parser.build(config_element);
-
- // Commit should push it to CfgMgr staging
- parser.commit();
-
- // Return the parser's local dicationary
- return (parser.local_dictionary_);
+ ClientClassDefListParser parser;
+ return (parser.parse(config_element, family));
}
};
// Verifies that given a valid expression, the ExpressionParser
// produces an Expression which can be evaluated against a v4 packet.
TEST_F(ExpressionParserTest, validExpression4) {
- testValidExpression<Pkt4>(Option::V4, "\"option[12].text == 'hundred4'\"",
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[12].text == 'hundred4'\"",
"hundred4");
}
// Verifies that the option name can be used in the evaluated expression.
TEST_F(ExpressionParserTest, validExpressionWithOptionName4) {
- testValidExpression<Pkt4>(Option::V4,
+ testValidExpression<Pkt4>(AF_INET,
"\"option[host-name].text == 'hundred4'\"",
"hundred4");
}
@@ -180,14 +167,15 @@ TEST_F(ExpressionParserTest, validExpressionWithOptionName4) {
// ExpressionParser produces an Expression which can be evaluated against
// a v4 packet.
TEST_F(ExpressionParserTest, validExpressionWithHex4) {
- testValidExpression<Pkt4>(Option::V4, "\"option[12].hex == 0x68756E6472656434\"",
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[12].hex == 0x68756E6472656434\"",
"hundred4");
}
// Verifies that the option name can be used together with .hex operator in
// the evaluated expression.
TEST_F(ExpressionParserTest, validExpressionWithOptionNameAndHex4) {
- testValidExpression<Pkt6>(Option::V4,
+ testValidExpression<Pkt6>(AF_INET,
"\"option[host-name].text == 0x68756E6472656434\"",
"hundred4");
}
@@ -195,13 +183,14 @@ TEST_F(ExpressionParserTest, validExpressionWithOptionNameAndHex4) {
// Verifies that given a valid expression, the ExpressionParser
// produces an Expression which can be evaluated against a v6 packet.
TEST_F(ExpressionParserTest, validExpression6) {
- testValidExpression<Pkt6>(Option::V6, "\"option[59].text == 'hundred6'\"",
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[59].text == 'hundred6'\"",
"hundred6");
}
// Verifies that the option name can be used in the evaluated expression.
TEST_F(ExpressionParserTest, validExpressionWithOptionName6) {
- testValidExpression<Pkt6>(Option::V6,
+ testValidExpression<Pkt6>(AF_INET6,
"\"option[bootfile-url].text == 'hundred6'\"",
"hundred6");
}
@@ -210,33 +199,32 @@ TEST_F(ExpressionParserTest, validExpressionWithOptionName6) {
// ExpressionParser produces an Expression which can be evaluated against
// a v6 packet.
TEST_F(ExpressionParserTest, validExpressionWithHex6) {
- testValidExpression<Pkt6>(Option::V6, "\"option[59].hex == 0x68756E6472656436\"",
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[59].hex == 0x68756E6472656436\"",
"hundred6");
}
// Verifies that the option name can be used together with .hex operator in
// the evaluated expression.
TEST_F(ExpressionParserTest, validExpressionWithOptionNameAndHex6) {
- testValidExpression<Pkt6>(Option::V6,
+ testValidExpression<Pkt6>(AF_INET6,
"\"option[bootfile-url].text == 0x68756E6472656436\"",
"hundred6");
}
// Verifies that an the ExpressionParser only accepts StringElements.
TEST_F(ExpressionParserTest, invalidExpressionElement) {
- ParserContextPtr context(new ParserContext(Option::V4));
- ExpressionParserPtr parser;
- ExpressionPtr parsed_expr;
-
// This will create an integer element should fail.
std::string cfg_txt = "777";
ElementPtr config_element = Element::fromJSON(cfg_txt);
// Create the parser.
- ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr,
- context)));
- // Expressionn build() should fail.
- ASSERT_THROW(parser->build(config_element), DhcpConfigError);
+ ExpressionPtr parsed_expr;
+ ExpressionParser parser;
+
+ // Expression parsing should fail.
+ ASSERT_THROW(parser.parse(parsed_expr, config_element, AF_INET6),
+ DhcpConfigError);
}
// Verifies that given an invalid expression with a syntax error,
@@ -245,19 +233,31 @@ TEST_F(ExpressionParserTest, invalidExpressionElement) {
// It is simply to ensure that if the parser fails, it does so
// Properly.
TEST_F(ExpressionParserTest, expressionSyntaxError) {
- ParserContextPtr context(new ParserContext(Option::V4));
- ExpressionParserPtr parser;
- ExpressionPtr parsed_expr;
-
// Turn config into elements.
std::string cfg_txt = "\"option 'bogus'\"";
ElementPtr config_element = Element::fromJSON(cfg_txt);
// Create the parser.
- ASSERT_NO_THROW(parser.reset(new ExpressionParser("", parsed_expr,
- context)));
- // Expressionn build() should fail.
- ASSERT_THROW(parser->build(config_element), DhcpConfigError);
+ ExpressionPtr parsed_expr;
+ ExpressionParser parser;
+
+ // Expression parsing should fail.
+ ASSERT_THROW(parser.parse(parsed_expr, config_element, AF_INET),
+ DhcpConfigError);
+}
+
+// Verifies that the name parameter is required and must not be empty
+TEST_F(ExpressionParserTest, nameEmpty) {
+ std::string cfg_txt = "{ \"name\": \"\" }";
+ ElementPtr config_element = Element::fromJSON(cfg_txt);
+
+ // Create the parser.
+ ExpressionPtr parsed_expr;
+ ExpressionParser parser;
+
+ // Expression parsing should fail.
+ ASSERT_THROW(parser.parse(parsed_expr, config_element, AF_INET6),
+ DhcpConfigError);
}
// Verifies you can create a class with only a name
@@ -270,7 +270,7 @@ TEST_F(ClientClassDefParserTest, nameOnlyValid) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
// We should find our class.
ASSERT_TRUE(cclass);
@@ -292,16 +292,18 @@ TEST_F(ClientClassDefParserTest, nameOnlyValid) {
// Verifies you can create a class with a name, expression,
// but no options.
+// @todo same with AF_INET6
TEST_F(ClientClassDefParserTest, nameAndExpressionClass) {
+ std::string test = "option[100].text == 'works right'";
std::string cfg_text =
"{ \n"
" \"name\": \"class_one\", \n"
- " \"test\": \"option[100].text == 'works right'\" \n"
+ " \"test\": \"" + test + "\" \n"
"} \n";
ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
// We should find our class.
ASSERT_TRUE(cclass);
@@ -321,18 +323,22 @@ TEST_F(ClientClassDefParserTest, nameAndExpressionClass) {
ExpressionPtr match_expr = cclass->getMatchExpr();
ASSERT_TRUE(match_expr);
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
// Build a packet that will fail evaluation.
Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
- EXPECT_FALSE(evaluate(*match_expr, *pkt4));
+ EXPECT_FALSE(evaluateBool(*match_expr, *pkt4));
// Now add the option so it will pass.
OptionPtr opt(new OptionString(Option::V4, 100, "works right"));
pkt4->addOption(opt);
- EXPECT_TRUE(evaluate(*match_expr, *pkt4));
+ EXPECT_TRUE(evaluateBool(*match_expr, *pkt4));
}
// Verifies you can create a class with a name and options,
// but no expression.
+// @todo same with AF_INET6
TEST_F(ClientClassDefParserTest, nameAndOptionsClass) {
std::string cfg_text =
@@ -350,7 +356,7 @@ TEST_F(ClientClassDefParserTest, nameAndOptionsClass) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
// We should find our class.
ASSERT_TRUE(cclass);
@@ -368,12 +374,14 @@ TEST_F(ClientClassDefParserTest, nameAndOptionsClass) {
// Verifies you can create a class with a name, expression,
// and options.
+// @todo same with AF_INET6
TEST_F(ClientClassDefParserTest, basicValidClass) {
+ std::string test = "option[100].text == 'booya'";
std::string cfg_text =
"{ \n"
" \"name\": \"MICROSOFT\", \n"
- " \"test\": \"option[100].text == 'booya'\", \n"
+ " \"test\": \"" + test + "\", \n"
" \"option-data\": [ \n"
" { \n"
" \"name\": \"domain-name-servers\", \n"
@@ -386,7 +394,7 @@ TEST_F(ClientClassDefParserTest, basicValidClass) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
// We should find our class.
ASSERT_TRUE(cclass);
@@ -401,14 +409,17 @@ TEST_F(ClientClassDefParserTest, basicValidClass) {
ExpressionPtr match_expr = cclass->getMatchExpr();
ASSERT_TRUE(match_expr);
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
// Build a packet that will fail evaluation.
Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
- EXPECT_FALSE(evaluate(*match_expr, *pkt4));
+ EXPECT_FALSE(evaluateBool(*match_expr, *pkt4));
// Now add the option so it will pass.
OptionPtr opt(new OptionString(Option::V4, 100, "booya"));
pkt4->addOption(opt);
- EXPECT_TRUE(evaluate(*match_expr, *pkt4));
+ EXPECT_TRUE(evaluateBool(*match_expr, *pkt4));
}
// Verifies that a class with no name, fails to parse.
@@ -429,7 +440,7 @@ TEST_F(ClientClassDefParserTest, noClassName) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET),
DhcpConfigError);
}
@@ -452,21 +463,7 @@ TEST_F(ClientClassDefParserTest, blankClassName) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
- DhcpConfigError);
-}
-
-
-// Verifies that a class with an unknown element, fails to parse.
-TEST_F(ClientClassDefParserTest, unknownElement) {
- std::string cfg_text =
- "{ \n"
- " \"name\": \"one\", \n"
- " \"bogus\": \"bad\" \n"
- "} \n";
-
- ClientClassDefPtr cclass;
- ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET),
DhcpConfigError);
}
@@ -479,7 +476,7 @@ TEST_F(ClientClassDefParserTest, invalidExpression) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6),
DhcpConfigError);
}
@@ -494,7 +491,7 @@ TEST_F(ClientClassDefParserTest, invalidOptionData) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_THROW(cclass = parseClientClassDef(cfg_text, Option::V4),
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET),
DhcpConfigError);
}
@@ -516,7 +513,7 @@ TEST_F(ClientClassDefListParserTest, simpleValidList) {
// Parsing the list should succeed.
ClientClassDictionaryPtr dictionary;
- ASSERT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4));
+ ASSERT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET6));
ASSERT_TRUE(dictionary);
// We should have three classes in the dictionary.
@@ -536,16 +533,9 @@ TEST_F(ClientClassDefListParserTest, simpleValidList) {
ASSERT_TRUE(cclass);
EXPECT_EQ("three", cclass->getName());
- // For good measure, make sure we can't find a non-existant class.
+ // For good measure, make sure we can't find a non-existent class.
ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
EXPECT_FALSE(cclass);
-
- // Verify that the dictionary was pushed to the CfgMgr's staging config.
- SrvConfigPtr staging = CfgMgr::instance().getStagingCfg();
- ASSERT_TRUE(staging);
- ClientClassDictionaryPtr staged_dictionary = staging->getClientClassDictionary();
- ASSERT_TRUE(staged_dictionary);
- EXPECT_TRUE(*staged_dictionary == *dictionary);
}
// Verifies that class list containing a duplicate class entries, fails
@@ -565,28 +555,13 @@ TEST_F(ClientClassDefListParserTest, duplicateClass) {
"] \n";
ClientClassDictionaryPtr dictionary;
- ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4),
- DhcpConfigError);
-}
-
-// Verifies that a class list containing an invalid class entry, fails to
-// parse.
-TEST_F(ClientClassDefListParserTest, invalidClass) {
- std::string cfg_text =
- "[ \n"
- " { \n"
- " \"name\": \"one\", \n"
- " \"bogus\": \"bad\" \n"
- " } \n"
- "] \n";
-
- ClientClassDictionaryPtr dictionary;
- ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, Option::V4),
+ ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET),
DhcpConfigError);
}
// Test verifies that without any class specified, the fixed fields have their
// default, empty value.
+// @todo same with AF_INET6
TEST_F(ClientClassDefParserTest, noFixedFields) {
std::string cfg_text =
@@ -601,7 +576,7 @@ TEST_F(ClientClassDefParserTest, noFixedFields) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
// We should find our class.
ASSERT_TRUE(cclass);
@@ -629,7 +604,7 @@ TEST_F(ClientClassDefParserTest, nextServer) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
// We should find our class.
ASSERT_TRUE(cclass);
@@ -666,8 +641,8 @@ TEST_F(ClientClassDefParserTest, nextServerBogus) {
" ] \n"
"} \n";
- EXPECT_THROW(parseClientClassDef(bogus_v6, Option::V4), DhcpConfigError);
- EXPECT_THROW(parseClientClassDef(bogus_junk, Option::V4), DhcpConfigError);
+ EXPECT_THROW(parseClientClassDef(bogus_v6, AF_INET), DhcpConfigError);
+ EXPECT_THROW(parseClientClassDef(bogus_junk, AF_INET), DhcpConfigError);
}
// Test verifies that it is possible to define server-hostname field and it
@@ -687,7 +662,7 @@ TEST_F(ClientClassDefParserTest, serverName) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
// We should find our class.
ASSERT_TRUE(cclass);
@@ -714,7 +689,7 @@ TEST_F(ClientClassDefParserTest, serverNameInvalid) {
" ] \n"
"} \n";
- EXPECT_THROW(parseClientClassDef(cfg_too_long, Option::V4), DhcpConfigError);
+ EXPECT_THROW(parseClientClassDef(cfg_too_long, AF_INET), DhcpConfigError);
}
@@ -735,7 +710,7 @@ TEST_F(ClientClassDefParserTest, filename) {
"} \n";
ClientClassDefPtr cclass;
- ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, Option::V4));
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
// We should find our class.
ASSERT_TRUE(cclass);
@@ -767,7 +742,7 @@ TEST_F(ClientClassDefParserTest, filenameBogus) {
" ] \n"
"} \n";
- EXPECT_THROW(parseClientClassDef(cfg_too_long, Option::V4), DhcpConfigError);
+ EXPECT_THROW(parseClientClassDef(cfg_too_long, AF_INET), DhcpConfigError);
}
diff --git a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc
index 0ac90d78a2..ffd716cab6 100644
--- a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc
+++ b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,7 +6,9 @@
#include <config.h>
#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/cfgmgr.h>
#include <dhcp/option_space.h>
+#include <testutils/test_to_element.h>
#include <exceptions/exceptions.h>
#include <boost/scoped_ptr.hpp>
#include <asiolink/io_address.h>
@@ -20,6 +22,7 @@ using namespace std;
using namespace isc::dhcp;
using namespace isc::util;
using namespace isc::asiolink;
+using namespace isc::test;
using namespace isc;
namespace {
@@ -44,7 +47,7 @@ TEST(ClientClassDef, construction) {
// Verify we get an empty collection of cfg_option
cfg_option = cclass->getCfgOption();
ASSERT_TRUE(cfg_option);
- //EXPECT_EQ(0, cfg_option->size());
+ EXPECT_TRUE(cfg_option->empty());
}
// Tests options operations. Note we just do the basics
@@ -205,15 +208,15 @@ TEST(ClientClassDictionary, basics) {
// Verify that we can add classes with both addClass variants
// First addClass(name, expression, cfg_option)
- ASSERT_NO_THROW(dictionary->addClass("cc1", expr, cfg_option));
- ASSERT_NO_THROW(dictionary->addClass("cc2", expr, cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("cc1", expr, "", cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("cc2", expr, "", cfg_option));
// Verify duplicate add attempt throws
- ASSERT_THROW(dictionary->addClass("cc2", expr, cfg_option),
+ ASSERT_THROW(dictionary->addClass("cc2", expr, "", cfg_option),
DuplicateClientClassDef);
// Verify that you cannot add a class with no name.
- ASSERT_THROW(dictionary->addClass("", expr, cfg_option), BadValue);
+ ASSERT_THROW(dictionary->addClass("", expr, "", cfg_option), BadValue);
// Now with addClass(class pointer)
ASSERT_NO_THROW(cclass.reset(new ClientClassDef("cc3", expr, cfg_option)));
@@ -222,7 +225,7 @@ TEST(ClientClassDictionary, basics) {
// Verify duplicate add attempt throws
ASSERT_THROW(dictionary->addClass(cclass), DuplicateClientClassDef);
- // Verify that you cannot add emtpy class pointer
+ // Verify that you cannot add empty class pointer
cclass.reset();
ASSERT_THROW(dictionary->addClass(cclass), BadValue);
@@ -242,7 +245,7 @@ TEST(ClientClassDictionary, basics) {
ASSERT_TRUE(cclass);
EXPECT_EQ("cc3", cclass->getName());
- // Verify the looking for non-existant returns empty pointer
+ // Verify the looking for non-existing returns empty pointer
ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
EXPECT_FALSE(cclass);
@@ -254,7 +257,7 @@ TEST(ClientClassDictionary, basics) {
ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
EXPECT_FALSE(cclass);
- // Verify that we can attempt to remove a non-existant class
+ // Verify that we can attempt to remove a non-existing class
// without harm.
ASSERT_NO_THROW(dictionary->removeClass("cc3"));
EXPECT_EQ(2, classes->size());
@@ -269,9 +272,9 @@ TEST(ClientClassDictionary, copyAndEquality) {
CfgOptionPtr options;
dictionary.reset(new ClientClassDictionary());
- ASSERT_NO_THROW(dictionary->addClass("one", expr, options));
- ASSERT_NO_THROW(dictionary->addClass("two", expr, options));
- ASSERT_NO_THROW(dictionary->addClass("three", expr, options));
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", options));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", options));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", options));
// Copy constructor should succeed.
ASSERT_NO_THROW(dictionary2.reset(new ClientClassDictionary(*dictionary)));
@@ -353,4 +356,65 @@ TEST(ClientClassDef, fixedFieldsBasics) {
}
+// Verifies the unparse method of option class definitions
+TEST(ClientClassDef, unparseDef) {
+ CfgMgr::instance().setFamily(AF_INET);
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ // Get a client class definition and fill it
+ std::string name = "class1";
+ ExpressionPtr expr;
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
+ std::string test = "option[12].text == 'foo'";
+ cclass->setTest(test);
+ std::string next_server = "1.2.3.4";
+ cclass->setNextServer(IOAddress(next_server));
+ std::string sname = "my-server.example.com";
+ cclass->setSname(sname);
+ std::string filename = "/boot/kernel";
+ cclass->setFilename(filename);
+
+ // Unparse it
+ std::string expected = "{\n"
+ "\"name\": \"" + name + "\",\n"
+ "\"test\": \"" + test + "\",\n"
+ "\"next-server\": \"" + next_server + "\",\n"
+ "\"server-hostname\": \"" + sname + "\",\n"
+ "\"boot-file-name\": \"" + filename + "\",\n"
+ "\"option-data\": [ ] }\n";
+ runToElementTest<ClientClassDef>(expected, *cclass);
+}
+
+// Verifies the unparse method of client class dictionaries
+TEST(ClientClassDictionary, unparseDict) {
+ CfgMgr::instance().setFamily(AF_INET);
+ ClientClassDictionaryPtr dictionary;
+ ExpressionPtr expr;
+ CfgOptionPtr options;
+
+ // Get a client class dictionary and fill it
+ dictionary.reset(new ClientClassDictionary());
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", options));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", options));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", options));
+
+ // Unparse it
+ auto add_defaults =
+ [](std::string name) {
+ return ("{\n"
+ "\"name\": \"" + name + "\",\n"
+ "\"next-server\": \"0.0.0.0\",\n"
+ "\"server-hostname\": \"\",\n"
+ "\"boot-file-name\": \"\",\n"
+ "\"option-data\": [ ] }");
+ };
+
+ std::string expected = "[\n" +
+ add_defaults("one") + ",\n" +
+ add_defaults("two") + ",\n" +
+ add_defaults("three") + "]\n";
+
+ runToElementTest<ClientClassDictionary>(expected, *dictionary);
+}
+
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc
index baa2f7190f..3da3658d1e 100644
--- a/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cql_lease_mgr_unittest.cc
@@ -643,4 +643,14 @@ TEST_F(CqlLeaseMgrTest, deleteExpiredReclaimedLeases4) {
testDeleteExpiredReclaimedLeases4();
}
+// Tests that leases from specific subnet can be removed.
+TEST_F(CqlLeaseMgrTest, DISABLED_wipeLeases4) {
+ testWipeLeases4();
+}
+
+// Tests that leases from specific subnet can be removed.
+TEST_F(CqlLeaseMgrTest, DISABLED_wipeLeases6) {
+ testWipeLeases6();
+}
+
}; // Of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/d2_client_unittest.cc b/src/lib/dhcpsrv/tests/d2_client_unittest.cc
index 229a1d8a43..a85cf0a8e5 100644
--- a/src/lib/dhcpsrv/tests/d2_client_unittest.cc
+++ b/src/lib/dhcpsrv/tests/d2_client_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#include <dhcp/option4_client_fqdn.h>
#include <dhcp/option6_client_fqdn.h>
#include <dhcpsrv/d2_client_mgr.h>
+#include <testutils/test_to_element.h>
#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -16,6 +17,7 @@ using namespace std;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::util;
+using namespace isc::test;
using namespace isc;
namespace {
@@ -121,6 +123,25 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
ASSERT_NO_THROW(std::cout << "toText test:" << std::endl <<
*d2_client_config << std::endl);
+ // Verify what toElement returns.
+ std::string expected = "{\n"
+ "\"enable-updates\": true,\n"
+ "\"server-ip\": \"127.0.0.1\",\n"
+ "\"server-port\": 477,\n"
+ "\"sender-ip\": \"127.0.0.1\",\n"
+ "\"sender-port\": 478,\n"
+ "\"max-queue-size\": 2048,\n"
+ "\"ncr-protocol\": \"UDP\",\n"
+ "\"ncr-format\": \"JSON\",\n"
+ "\"always-include-fqdn\": true,\n"
+ "\"override-no-update\": true,\n"
+ "\"override-client-update\": true,\n"
+ "\"replace-client-name\": \"when-present\",\n"
+ "\"generated-prefix\": \"the_prefix\",\n"
+ "\"qualifying-suffix\": \"the.suffix.\"\n"
+ "}\n";
+ runToElementTest<D2ClientConfig>(expected, *d2_client_config);
+
// Verify that constructor does not allow use of NCR_TCP.
/// @todo obviously this becomes invalid once TCP is supported.
ASSERT_THROW(d2_client_config.reset(new
@@ -769,7 +790,7 @@ TEST(D2ClientMgr, adjustDomainNameV4) {
ASSERT_EQ(D2ClientConfig::RCM_NEVER, cfg->getReplaceClientNameMode());
// replace-client-name is false, client passes in empty fqdn
- // reponse domain should be empty/partial.
+ // response domain should be empty/partial.
request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
"", Option4ClientFqdn::PARTIAL));
response.reset(new Option4ClientFqdn(*request));
@@ -812,7 +833,7 @@ TEST(D2ClientMgr, adjustDomainNameV4) {
ASSERT_EQ(D2ClientConfig::RCM_WHEN_PRESENT, cfg->getReplaceClientNameMode());
// replace-client-name is true, client passes in empty fqdn
- // reponse domain should be empty/partial.
+ // response domain should be empty/partial.
request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
"", Option4ClientFqdn::PARTIAL));
response.reset(new Option4ClientFqdn(*request));
@@ -822,7 +843,7 @@ TEST(D2ClientMgr, adjustDomainNameV4) {
EXPECT_EQ(Option4ClientFqdn::PARTIAL, response->getDomainNameType());
// replace-client-name is true, client passes in a partial fqdn
- // reponse domain should be empty/partial.
+ // response domain should be empty/partial.
request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
"myhost", Option4ClientFqdn::PARTIAL));
response.reset(new Option4ClientFqdn(*request));
@@ -833,7 +854,7 @@ TEST(D2ClientMgr, adjustDomainNameV4) {
// replace-client-name is true, client passes in a full fqdn
- // reponse domain should be empty/partial.
+ // response domain should be empty/partial.
request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
"myhost.example.com.",
Option4ClientFqdn::FULL));
@@ -862,7 +883,7 @@ TEST(D2ClientMgr, adjustDomainNameV6) {
ASSERT_EQ(D2ClientConfig::RCM_NEVER, cfg->getReplaceClientNameMode());
// replace-client-name is false, client passes in empty fqdn
- // reponse domain should be empty/partial.
+ // response domain should be empty/partial.
request.reset(new Option6ClientFqdn(0, "", Option6ClientFqdn::PARTIAL));
response.reset(new Option6ClientFqdn(*request));
@@ -902,7 +923,7 @@ TEST(D2ClientMgr, adjustDomainNameV6) {
ASSERT_EQ(D2ClientConfig::RCM_WHEN_PRESENT, cfg->getReplaceClientNameMode());
// replace-client-name is true, client passes in empty fqdn
- // reponse domain should be empty/partial.
+ // response domain should be empty/partial.
request.reset(new Option6ClientFqdn(0, "", Option6ClientFqdn::PARTIAL));
response.reset(new Option6ClientFqdn(*request));
@@ -911,7 +932,7 @@ TEST(D2ClientMgr, adjustDomainNameV6) {
EXPECT_EQ(Option6ClientFqdn::PARTIAL, response->getDomainNameType());
// replace-client-name is true, client passes in a partial fqdn
- // reponse domain should be empty/partial.
+ // response domain should be empty/partial.
request.reset(new Option6ClientFqdn(0, "myhost",
Option6ClientFqdn::PARTIAL));
response.reset(new Option6ClientFqdn(*request));
@@ -922,7 +943,7 @@ TEST(D2ClientMgr, adjustDomainNameV6) {
// replace-client-name is true, client passes in a full fqdn
- // reponse domain should be empty/partial.
+ // response domain should be empty/partial.
request.reset(new Option6ClientFqdn(0, "myhost.example.com.",
Option6ClientFqdn::FULL));
response.reset(new Option6ClientFqdn(*request));
diff --git a/src/lib/dhcpsrv/tests/d2_udp_unittest.cc b/src/lib/dhcpsrv/tests/d2_udp_unittest.cc
index 19b4f5f2b7..beb9bccdc7 100644
--- a/src/lib/dhcpsrv/tests/d2_udp_unittest.cc
+++ b/src/lib/dhcpsrv/tests/d2_udp_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -27,8 +27,8 @@ using namespace isc;
namespace {
-/// @brief Test fixture for excerising D2ClientMgr send management
-/// services. It inherents from D2ClientMgr to allow overriding various
+/// @brief Test fixture for exercising D2ClientMgr send management
+/// services. It inherits from D2ClientMgr to allow overriding various
/// methods and accessing otherwise restricted member. In particular it
/// overrides the NameChangeSender completion completion callback, allowing
/// the injection of send errors.
@@ -128,10 +128,10 @@ public:
/// @brief Overrides base class completion callback.
///
/// This method will be invoked each time a send completes. It allows
- /// intervention prior to calling the production implemenation in the
+ /// intervention prior to calling the production implementation in the
/// base. If simulate_send_failure_ is true, the base call impl will
/// be called with an error status, otherwise it will be called with
- /// the result paramater given.
+ /// the result parameter given.
///
/// @param result Result code of the send operation.
/// @param ncr NameChangeRequest which failed to send.
@@ -170,7 +170,7 @@ public:
return (boost::bind(&D2ClientMgrTest::error_handler, this, _1, _2));
}
- /// @brief Contructs a NameChangeRequest message from a fixed JSON string.
+ /// @brief Constructs a NameChangeRequest message from a fixed JSON string.
dhcp_ddns::NameChangeRequestPtr buildTestNcr() {
// Build an NCR from json string.
const char* ncr_str =
@@ -483,7 +483,7 @@ TEST_F(D2ClientMgrTest, udpSuspendUpdates) {
EXPECT_FALSE(amSending());
// Stopping the sender should have completed the second message's
- // in-progess send, so queue size should be 1.
+ // in-progress send, so queue size should be 1.
ASSERT_EQ(1, getQueueSize());
}
diff --git a/src/lib/dhcpsrv/tests/daemon_unittest.cc b/src/lib/dhcpsrv/tests/daemon_unittest.cc
index aa7d1a7a81..80765a4ea1 100644
--- a/src/lib/dhcpsrv/tests/daemon_unittest.cc
+++ b/src/lib/dhcpsrv/tests/daemon_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -262,7 +262,7 @@ TEST_F(DaemonTest, PIDFileCleanup) {
}
// Checks that configureLogger method is behaving properly.
-// More dedicated tests are availablef for LogConfigParser class.
+// More dedicated tests are available for LogConfigParser class.
// See logger_unittest.cc
TEST_F(DaemonTest, parsingConsoleOutput) {
CfgMgr::instance().setVerbose(false);
diff --git a/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
index e71dc2530f..7216351e3f 100644
--- a/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#include <cc/command_interpreter.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/parsers/dhcp_config_parser.h>
#include <dhcpsrv/parsers/dbaccess_parser.h>
#include <dhcpsrv/testutils/mysql_schema.h>
#include <dhcpsrv/host_mgr.h>
@@ -43,7 +44,7 @@ public:
/// (the last in particular).
///
/// As some of the tests have the side-effect of altering the logging
- /// settings (when the parser's "build" method is called), ensure that
+ /// settings (when the parser's "parse" method is called), ensure that
/// the logging is reset to the default after each test completes.
~DbAccessParserTest() {
LeaseMgrFactory::destroy();
@@ -177,6 +178,7 @@ private:
bool quoteValue(const std::string& parameter) const {
return ((parameter != "persist") && (parameter != "lfc-interval") &&
(parameter != "connect-timeout") &&
+ (parameter != "port") &&
(parameter != "readonly"));
}
@@ -194,14 +196,20 @@ public:
/// @brief Constructor
///
/// @brief Keyword/value collection of database access parameters
- TestDbAccessParser(const std::string& param_name, DbAccessParser::DBType type)
- : DbAccessParser(param_name, type)
+ TestDbAccessParser(DbAccessParser::DBType type)
+ : DbAccessParser(type)
{}
/// @brief Destructor
virtual ~TestDbAccessParser()
{}
+ /// @brief Parse configuration value
+ void parse(ConstElementPtr database_config) {
+ CfgDbAccessPtr cfg_db(new CfgDbAccess());
+ DbAccessParser::parse(cfg_db, database_config);
+ }
+
/// Allow use of superclass's protected functions.
using DbAccessParser::getDbAccessParameters;
using DbAccessParser::getDbAccessString;
@@ -236,8 +244,8 @@ TEST_F(DbAccessParserTest, validTypeMemfile) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_NO_THROW(parser.build(json_elements));
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
}
@@ -252,8 +260,8 @@ TEST_F(DbAccessParserTest, emptyKeyword) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_NO_THROW(parser.build(json_elements));
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
}
@@ -269,8 +277,8 @@ TEST_F(DbAccessParserTest, persistV4Memfile) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_NO_THROW(parser.build(json_elements));
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Valid memfile", parser.getDbAccessParameters(),
config);
@@ -288,8 +296,8 @@ TEST_F(DbAccessParserTest, persistV6Memfile) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_NO_THROW(parser.build(json_elements));
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Valid memfile", parser.getDbAccessParameters(),
config);
@@ -307,8 +315,8 @@ TEST_F(DbAccessParserTest, validLFCInterval) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_NO_THROW(parser.build(json_elements));
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Valid LFC Interval", parser.getDbAccessParameters(),
config);
}
@@ -325,8 +333,8 @@ TEST_F(DbAccessParserTest, negativeLFCInterval) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_THROW(parser.build(json_elements), BadValue);
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_THROW(parser.parse(json_elements), DhcpConfigError);
}
// This test checks that the parser rejects the too large (greater than
@@ -341,8 +349,8 @@ TEST_F(DbAccessParserTest, largeLFCInterval) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_THROW(parser.build(json_elements), BadValue);
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_THROW(parser.parse(json_elements), DhcpConfigError);
}
// This test checks that the parser accepts the valid value of the
@@ -357,8 +365,8 @@ TEST_F(DbAccessParserTest, validTimeout) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_NO_THROW(parser.build(json_elements));
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Valid timeout", parser.getDbAccessParameters(),
config);
}
@@ -375,8 +383,8 @@ TEST_F(DbAccessParserTest, negativeTimeout) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_THROW(parser.build(json_elements), BadValue);
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_THROW(parser.parse(json_elements), DhcpConfigError);
}
// This test checks that the parser rejects a too large (greater than
@@ -391,14 +399,65 @@ TEST_F(DbAccessParserTest, largeTimeout) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_THROW(parser.build(json_elements), BadValue);
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_THROW(parser.parse(json_elements), DhcpConfigError);
+}
+
+// This test checks that the parser accepts the valid value of the
+// port parameter.
+TEST_F(DbAccessParserTest, validPort) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/kea/var/kea-leases6.csv",
+ "port", "3306",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid port", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// port parameter.
+TEST_F(DbAccessParserTest, negativePort) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/kea/var/kea-leases6.csv",
+ "port", "-1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_THROW(parser.parse(json_elements), DhcpConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint16_t) value of the timeout parameter.
+TEST_F(DbAccessParserTest, largePort) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/kea/var/kea-leases6.csv",
+ "port", "65536",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_THROW(parser.parse(json_elements), DhcpConfigError);
}
// Check that the parser works with a valid MySQL configuration
TEST_F(DbAccessParserTest, validTypeMysql) {
const char* config[] = {"type", "mysql",
"host", "erewhon",
+ "port", "3306",
"user", "kea",
"password", "keapassword",
"name", "keatest",
@@ -408,14 +467,15 @@ TEST_F(DbAccessParserTest, validTypeMysql) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_NO_THROW(parser.build(json_elements));
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Valid mysql", parser.getDbAccessParameters(), config);
}
// A missing 'type' keyword should cause an exception to be thrown.
TEST_F(DbAccessParserTest, missingTypeKeyword) {
const char* config[] = {"host", "erewhon",
+ "port", "3306",
"user", "kea",
"password", "keapassword",
"name", "keatest",
@@ -425,20 +485,8 @@ TEST_F(DbAccessParserTest, missingTypeKeyword) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_THROW(parser.build(json_elements), TypeKeywordMissing);
-}
-
-// Check that the factory function works.
-TEST_F(DbAccessParserTest, factory) {
-
- // Check that the parser is built through the factory.
- boost::scoped_ptr<DhcpConfigParser> parser(
- DbAccessParser::factory("lease-database")
- );
- EXPECT_TRUE(parser);
- DbAccessParser* dbap = dynamic_cast<DbAccessParser*>(parser.get());
- EXPECT_NE(static_cast<DbAccessParser*>(NULL), dbap);
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_THROW(parser.parse(json_elements), DhcpConfigError);
}
// Check reconfiguration. Checks that incremental changes applied to the
@@ -450,6 +498,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
// Applying config2 will cause a wholesale change.
const char* config2[] = {"type", "mysql",
"host", "erewhon",
+ "port", "3306",
"user", "kea",
"password", "keapassword",
"name", "keatest",
@@ -461,6 +510,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
NULL};
const char* config3[] = {"type", "mysql",
"host", "erewhon",
+ "port", "3306",
"user", "me",
"password", "meagain",
"name", "keatest",
@@ -480,12 +530,13 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
NULL};
const char* config4[] = {"type", "mysql",
"host", "erewhon",
+ "port", "3306",
"user", "them",
"password", "",
"name", "keatest",
NULL};
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
// First configuration string should cause a representation of that string
// to be held.
@@ -493,7 +544,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- EXPECT_NO_THROW(parser.build(json_elements));
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Initial configuration", parser.getDbAccessParameters(),
config1);
@@ -503,7 +554,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- EXPECT_NO_THROW(parser.build(json_elements));
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Subsequent configuration", parser.getDbAccessParameters(),
config2);
@@ -513,7 +564,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- EXPECT_NO_THROW(parser.build(json_elements));
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Incremental configuration", parser.getDbAccessParameters(),
config3);
@@ -523,7 +574,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- EXPECT_THROW(parser.build(json_elements), BadValue);
+ EXPECT_THROW(parser.parse(json_elements), DhcpConfigError);
checkAccessString("Incompatible incremental change", parser.getDbAccessParameters(),
config3);
@@ -533,7 +584,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- EXPECT_NO_THROW(parser.build(json_elements));
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Compatible incremental change", parser.getDbAccessParameters(),
config4);
}
@@ -541,7 +592,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
// Check that the database access string is constructed correctly.
TEST_F(DbAccessParserTest, getDbAccessString) {
const char* config[] = {"type", "mysql",
- "host", "" ,
+ "host", "",
"name", "keatest",
NULL};
@@ -549,8 +600,8 @@ TEST_F(DbAccessParserTest, getDbAccessString) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_NO_THROW(parser.build(json_elements));
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.parse(json_elements));
// Get the database access string
std::string dbaccess = parser.getDbAccessString();
@@ -575,8 +626,8 @@ TEST_F(DbAccessParserTest, validReadOnly) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_NO_THROW(parser.build(json_elements));
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_NO_THROW(parser.parse(json_elements));
checkAccessString("Valid readonly parameter",
parser.getDbAccessParameters(),
@@ -597,8 +648,8 @@ TEST_F(DbAccessParserTest, invalidReadOnly) {
ConstElementPtr json_elements = Element::fromJSON(json_config);
EXPECT_TRUE(json_elements);
- TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB);
- EXPECT_THROW(parser.build(json_elements), BadValue);
+ TestDbAccessParser parser(DbAccessParser::LEASE_DB);
+ EXPECT_THROW(parser.parse(json_elements), DhcpConfigError);
}
diff --git a/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc b/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc
index 5f4af2459e..07ac8bd326 100644
--- a/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc
+++ b/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -64,7 +64,7 @@ protected:
/// @param msg_type Message type.
/// @param postfix Postfix to be appended to the remote address. For example,
/// for postfix = 5 the resulting remote address will be 2001:db8:1::5.
- /// The postifx value is also used to generate the postfix for the interface.
+ /// The postfix value is also used to generate the postfix for the interface.
/// The possible interface names are "eth0" and "eth1". For even postfix values
/// the "eth0" will be used, for odd postfix values "eth1" will be used.
///
@@ -164,7 +164,7 @@ Dhcp4o6IpcBaseTest::createDHCPv4o6Message(uint16_t msg_type,
// the servers. The receiving server will check that such interface
// is present in the system. The fake configuration we're using for
// this test includes two interfaces: "eth0" and "eth1". Therefore,
- // we pick one or another, depending on the index of the interation.
+ // we pick one or another, depending on the index of the iteration.
pkt->setIface(concatenate("eth", postfix % 2));
// The remote address of the sender of the DHCPv6 packet is carried
@@ -260,7 +260,7 @@ Dhcp4o6IpcBaseTest::testSendReceive(uint16_t iterations_num,
// in the source packet.
has_vendor_option.push_back(static_cast<bool>(pkt->getOption(D6O_VENDOR_OPTS)));
- // Actaully send the message through the IPC.
+ // Actually send the message through the IPC.
ASSERT_NO_THROW(ipc_src.send(pkt))
<< "Failed to send the message over the IPC for iteration " << i;
}
@@ -449,7 +449,7 @@ TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutVendorOption) {
testReceiveError(pkt);
}
-// This test verifies that receving packet over the IPC fails when the
+// This test verifies that receiving packet over the IPC fails when the
// enterprise ID carried in the vendor option is invalid.
TEST_F(Dhcp4o6IpcBaseTest, receiveInvalidEnterpriseId) {
Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
@@ -501,7 +501,7 @@ TEST_F(Dhcp4o6IpcBaseTest, receiveWithInvalidInterface) {
}
-// This test verifies that receving packet over the IPC fails when the
+// This test verifies that receiving packet over the IPC fails when the
// source address option is not present.
TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutSourceAddressOption) {
Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
index eeb7c96b87..9c2b04f4d7 100644
--- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
+++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,19 +7,24 @@
#include <config.h>
#include <cc/command_interpreter.h>
#include <cc/data.h>
+#include <cc/simple_parser.h>
#include <dhcp/option.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfg_mac_source.h>
#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
#include <dhcpsrv/tests/test_libraries.h>
#include <dhcpsrv/testutils/config_result_check.h>
#include <exceptions/exceptions.h>
+#include <hooks/hooks_parser.h>
#include <hooks/hooks_manager.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
#include <boost/foreach.hpp>
@@ -37,6 +42,7 @@ using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
using namespace isc::hooks;
+using namespace isc::test;
namespace {
@@ -187,7 +193,7 @@ TEST_F(DhcpParserTest, uint32ParserTest) {
Uint32StoragePtr storage(new Uint32Storage());
Uint32Parser parser(name, storage);
- // Verify that parser with rejects a non-interger element.
+ // Verify that parser with rejects a non-integer element.
ElementPtr wrong_element = Element::create("I am a string");
EXPECT_THROW(parser.build(wrong_element), isc::BadValue);
@@ -225,55 +231,103 @@ TEST_F(DhcpParserTest, uint32ParserTest) {
EXPECT_EQ(test_value, actual_value);
}
-/// @brief Check MACSourcesListConfigParser basic functionality
+/// Verifies the code that parses mac sources and adds them to CfgMgr
+TEST_F(DhcpParserTest, MacSources) {
+
+ // That's an equivalent of the following snippet:
+ // "mac-sources: [ \"duid\", \"ipv6\" ]";
+ ElementPtr values = Element::createList();
+ values->add(Element::create("duid"));
+ values->add(Element::create("ipv6-link-local"));
+
+ // Let's grab server configuration from CfgMgr
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ CfgMACSource& sources = cfg->getMACSources();
+
+ // This should parse the configuration and check that it doesn't throw.
+ MACSourcesListConfigParser parser;
+ EXPECT_NO_THROW(parser.parse(sources, values));
+
+ // Finally, check the sources that were configured
+ CfgMACSources configured_sources = cfg->getMACSources().get();
+ ASSERT_EQ(2, configured_sources.size());
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, configured_sources[0]);
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, configured_sources[1]);
+}
+
+/// @brief Check MACSourcesListConfigParser rejecting empty list
///
-/// Verifies that the parser:
-/// 1. Does not allow empty for storage.
-/// 2. Does not allow name other than "mac-sources"
-/// 3. Parses list of mac sources and adds them to CfgMgr
-TEST_F(DhcpParserTest, MacSourcesListConfigParserTest) {
+/// Verifies that the code rejects an empty mac-sources list.
+TEST_F(DhcpParserTest, MacSourcesEmpty) {
- const std::string valid_name = "mac-sources";
- const std::string bogus_name = "bogus-name";
+ // That's an equivalent of the following snippet:
+ // "mac-sources: [ \"duid\", \"ipv6\" ]";
+ ElementPtr values = Element::createList();
- ParserContextPtr parser_context(new ParserContext(Option::V6));
+ // Let's grab server configuration from CfgMgr
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ CfgMACSource& sources = cfg->getMACSources();
- // Verify that parser constructor fails if parameter name isn't "mac-sources"
- EXPECT_THROW(MACSourcesListConfigParser(bogus_name, parser_context),
- isc::BadValue);
+ // This should throw, because if specified, at least one MAC source
+ // has to be specified.
+ MACSourcesListConfigParser parser;
+ EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
+}
+
+/// @brief Check MACSourcesListConfigParser rejecting empty list
+///
+/// Verifies that the code rejects fake mac source.
+TEST_F(DhcpParserTest, MacSourcesBogus) {
// That's an equivalent of the following snippet:
// "mac-sources: [ \"duid\", \"ipv6\" ]";
- ElementPtr config = Element::createList();
- config->add(Element::create("duid"));
- config->add(Element::create("ipv6-link-local"));
+ ElementPtr values = Element::createList();
+ values->add(Element::create("from-ebay"));
+ values->add(Element::create("just-guess-it"));
- boost::scoped_ptr<MACSourcesListConfigParser>
- parser(new MACSourcesListConfigParser(valid_name, parser_context));
+ // Let's grab server configuration from CfgMgr
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ CfgMACSource& sources = cfg->getMACSources();
+
+ // This should throw, because these are not valid sources.
+ MACSourcesListConfigParser parser;
+ EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
+}
- // This should parse the configuration and add eth0 and eth1 to the list
- // of interfaces that server should listen on.
- EXPECT_NO_THROW(parser->build(config));
- EXPECT_NO_THROW(parser->commit());
+/// Verifies the code that properly catches duplicate entries
+/// in mac-sources definition.
+TEST_F(DhcpParserTest, MacSourcesDuplicate) {
- // Use CfgMgr instance to check if eth0 and eth1 was added, and that
- // eth2 was not added.
+ // That's an equivalent of the following snippet:
+ // "mac-sources: [ \"duid\", \"ipv6\" ]";
+ ElementPtr values = Element::createList();
+ values->add(Element::create("ipv6-link-local"));
+ values->add(Element::create("duid"));
+ values->add(Element::create("duid"));
+ values->add(Element::create("duid"));
+
+ // Let's grab server configuration from CfgMgr
SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
ASSERT_TRUE(cfg);
- CfgMACSources configured_sources = cfg->getMACSources().get();
+ CfgMACSource& sources = cfg->getMACSources();
- ASSERT_EQ(2, configured_sources.size());
- EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, configured_sources[0]);
- EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, configured_sources[1]);
+ // This should parse the configuration and check that it throws.
+ MACSourcesListConfigParser parser;
+ EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
}
+
/// @brief Test Fixture class which provides basic structure for testing
/// configuration parsing. This is essentially the same structure provided
/// by dhcp servers.
class ParseConfigTest : public ::testing::Test {
public:
/// @brief Constructor
- ParseConfigTest() {
+ ParseConfigTest()
+ :family_(AF_INET6) {
reset_context();
CfgMgr::instance().clear();
}
@@ -292,7 +346,7 @@ public:
/// @return returns an ConstElementPtr containing the numeric result
/// code and outcome comment.
isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr
- config_set) {
+ config_set) {
// Answer will hold the result.
ConstElementPtr answer;
if (!config_set) {
@@ -301,33 +355,69 @@ public:
return (answer);
}
- // option parsing must be done last, so save it if we hit if first
- ParserPtr option_parser;
-
ConfigPair config_pair;
try {
// Iterate over the config elements.
const std::map<std::string, ConstElementPtr>& values_map =
config_set->mapValue();
BOOST_FOREACH(config_pair, values_map) {
- // Create the parser based on element name.
- ParserPtr parser(createConfigParser(config_pair.first));
- // Options must be parsed last
- if (config_pair.first == "option-data") {
- option_parser = parser;
- } else {
- // Anything else we can call build straight away.
- parser->build(config_pair.second);
- parser->commit();
+
+ // These are the simple parsers. No need to go through
+ // the ParserPtr hooplas with them.
+ if ((config_pair.first == "option-data") ||
+ (config_pair.first == "option-def") ||
+ (config_pair.first == "dhcp-ddns")) {
+ continue;
+ }
+
+ // We also don't care about the default values that may be been
+ // inserted
+ if ((config_pair.first == "preferred-lifetime") ||
+ (config_pair.first == "valid-lifetime") ||
+ (config_pair.first == "renew-timer") ||
+ (config_pair.first == "rebind-timer")) {
+ continue;
+ }
+
+ if (config_pair.first == "hooks-libraries") {
+ HooksLibrariesParser hook_parser;
+ HooksConfig& libraries =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ hook_parser.parse(libraries, config_pair.second);
+ libraries.verifyLibraries(config_pair.second->getPosition());
+ libraries.loadLibraries();
+ continue;
}
}
+ // The option definition parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator
+ def_config = values_map.find("option-def");
+ if (def_config != values_map.end()) {
+
+ CfgOptionDefPtr cfg_def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef();
+ OptionDefListParser def_list_parser;
+ def_list_parser.parse(cfg_def, def_config->second);
+ }
+
// The option values parser is the next one to be run.
std::map<std::string, ConstElementPtr>::const_iterator
option_config = values_map.find("option-data");
if (option_config != values_map.end()) {
- option_parser->build(option_config->second);
- option_parser->commit();
+ CfgOptionPtr cfg_option = CfgMgr::instance().getStagingCfg()->getCfgOption();
+
+ OptionDataListParser option_list_parser(family_);
+ option_list_parser.parse(cfg_option, option_config->second);
+ }
+
+ // The dhcp-ddns parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator
+ d2_client_config = values_map.find("dhcp-ddns");
+ if (d2_client_config != values_map.end()) {
+ // Used to be done by parser commit
+ D2ClientConfigParser parser;
+ D2ClientConfigPtr cfg = parser.parse(d2_client_config->second);
+ CfgMgr::instance().setD2ClientConfig(cfg);
}
// Everything was fine. Configuration is successful.
@@ -344,44 +434,85 @@ public:
return (answer);
}
- /// @brief Create an element parser based on the element name.
+ /// @brief DHCP-specific method that sets global, and option specific defaults
///
- /// Creates a parser for the appropriate element and stores a pointer to it
- /// in the appropriate class variable.
+ /// This method sets the defaults in the global scope, in option definitions,
+ /// and in option data.
///
- /// Note that the method currently it only supports option-defs, option-data
- /// and hooks-libraries.
+ /// @param global pointer to the Element tree that holds configuration
+ /// @param global_defaults array with global default values
+ /// @param option_defaults array with option-data default values
+ /// @param option_def_defaults array with default values for option definitions
+ /// @return number of default values inserted.
+ size_t setAllDefaults(isc::data::ElementPtr global,
+ const SimpleDefaults& global_defaults,
+ const SimpleDefaults& option_defaults,
+ const SimpleDefaults& option_def_defaults) {
+ size_t cnt = 0;
+ // Set global defaults first.
+ cnt = SimpleParser::setDefaults(global, global_defaults);
+
+ // Now set option definition defaults for each specified option definition
+ ConstElementPtr option_defs = global->get("option-def");
+ if (option_defs) {
+ BOOST_FOREACH(ElementPtr single_def, option_defs->listValue()) {
+ cnt += SimpleParser::setDefaults(single_def, option_def_defaults);
+ }
+ }
+
+ ConstElementPtr options = global->get("option-data");
+ if (options) {
+ BOOST_FOREACH(ElementPtr single_option, options->listValue()) {
+ cnt += SimpleParser::setDefaults(single_option, option_defaults);
+ }
+ }
+
+ return (cnt);
+ }
+
+ /// This table defines default values for option definitions in DHCPv6
+ static const SimpleDefaults OPTION6_DEF_DEFAULTS;
+
+ /// This table defines default values for option definitions in DHCPv4
+ static const SimpleDefaults OPTION4_DEF_DEFAULTS;
+
+ /// This table defines default values for options in DHCPv6
+ static const SimpleDefaults OPTION6_DEFAULTS;
+
+ /// This table defines default values for options in DHCPv4
+ static const SimpleDefaults OPTION4_DEFAULTS;
+
+ /// This table defines default values for both DHCPv4 and DHCPv6
+ static const SimpleDefaults GLOBAL6_DEFAULTS;
+
+ /// @brief sets all default values for DHCPv4 and DHCPv6
///
- /// @param config_id is the name of the configuration element.
+ /// This function largely duplicates what SimpleParser4 and SimpleParser6 classes
+ /// provide. However, since there are tons of unit-tests in dhcpsrv that need
+ /// this functionality and there are good reasons to keep those classes in
+ /// src/bin/dhcp{4,6}, the most straightforward way is to simply copy the
+ /// minimum code here. Hence this method.
///
- /// @return returns a shared pointer to DhcpConfigParser.
+ /// @todo - TKM, I think this is fairly hideous and we should figure out a
+ /// a way to not have to replicate in this fashion. It may be minimum code
+ /// now, but it won't be fairly soon.
///
- /// @throw throws NotImplemented if element name isn't supported.
- ParserPtr createConfigParser(const std::string& config_id) {
- ParserPtr parser;
- int family = parser_context_->universe_ == Option::V4 ?
- AF_INET : AF_INET6;
- if (config_id.compare("option-data") == 0) {
- parser.reset(new OptionDataListParser(config_id, CfgOptionPtr(),
- family));
-
- } else if (config_id.compare("option-def") == 0) {
- parser.reset(new OptionDefListParser(config_id,
- parser_context_));
-
- } else if (config_id.compare("hooks-libraries") == 0) {
- parser.reset(new HooksLibrariesParser(config_id));
- hooks_libraries_parser_ =
- boost::dynamic_pointer_cast<HooksLibrariesParser>(parser);
- } else if (config_id.compare("dhcp-ddns") == 0) {
- parser.reset(new D2ClientConfigParser(config_id));
+ /// @param config configuration structure to be filled with default values
+ /// @param v6 true = DHCPv6, false = DHCPv4
+ void setAllDefaults(ElementPtr config, bool v6) {
+ if (v6) {
+ setAllDefaults(config, GLOBAL6_DEFAULTS, OPTION6_DEFAULTS,
+ OPTION6_DEF_DEFAULTS);
} else {
- isc_throw(NotImplemented,
- "Parser error: configuration parameter not supported: "
- << config_id);
+ setAllDefaults(config, GLOBAL6_DEFAULTS, OPTION4_DEFAULTS,
+ OPTION4_DEF_DEFAULTS);
}
- return (parser);
+ /// D2 client configuration code is in this library
+ ConstElementPtr d2_client = config->get("dhcp-ddns");
+ if (d2_client) {
+ D2ClientConfigParser::setAllDefaults(d2_client);
+ }
}
/// @brief Convenience method for parsing a configuration
@@ -390,21 +521,24 @@ public:
/// and parse them.
/// @param config is the configuration string to parse
///
- /// @return retuns 0 if the configuration parsed successfully,
+ /// @return returns 0 if the configuration parsed successfully,
/// non-zero otherwise failure.
- int parseConfiguration(const std::string& config) {
+ int parseConfiguration(const std::string& config, bool v6 = false) {
int rcode_ = 1;
// Turn config into elements.
// Test json just to make sure its valid.
ElementPtr json = Element::fromJSON(config);
EXPECT_TRUE(json);
if (json) {
+ setAllDefaults(json, v6);
+
ConstElementPtr status = parseElementSet(json);
ConstElementPtr comment = parseAnswer(rcode_, status);
error_text_ = comment->stringValue();
// If error was reported, the error string should contain
// position of the data element which caused failure.
if (rcode_ != 0) {
+ std::cout << "Error text:" << error_text_ << std::endl;
EXPECT_TRUE(errorContainsPosition(status, "<string>"));
}
}
@@ -447,7 +581,7 @@ public:
void reset_context(){
// Note set context universe to V6 as it has to be something.
CfgMgr::instance().clear();
- parser_context_.reset(new ParserContext(Option::V6));
+ family_ = AF_INET6;
// Ensure no hooks libraries are loaded.
HooksManager::unloadLibraries();
@@ -457,18 +591,132 @@ public:
CfgMgr::instance().setD2ClientConfig(tmp);
}
- /// @brief Parsers used in the parsing of the configuration
- ///
- /// Allows the tests to interrogate the state of the parsers (if required).
- boost::shared_ptr<HooksLibrariesParser> hooks_libraries_parser_;
+ /// Allows the tests to interrogate the state of the libraries (if required).
+ const isc::hooks::HookLibsCollection& getLibraries() {
+ return (CfgMgr::instance().getStagingCfg()->getHooksConfig().get());
+ }
- /// @brief Parser context - provides storage for options and definitions
- ParserContextPtr parser_context_;
+ /// @brief specifies IP protocol family (AF_INET or AF_INET6)
+ uint16_t family_;
/// @brief Error string if the parsing failed
std::string error_text_;
};
+/// This table defines default values for option definitions in DHCPv6
+const SimpleDefaults ParseConfigTest::OPTION6_DEF_DEFAULTS = {
+ { "record-types", Element::string, ""},
+ { "space", Element::string, "dhcp6"},
+ { "array", Element::boolean, "false"},
+ { "encapsulate", Element::string, "" }
+};
+
+/// This table defines default values for option definitions in DHCPv4
+const SimpleDefaults ParseConfigTest::OPTION4_DEF_DEFAULTS = {
+ { "record-types", Element::string, ""},
+ { "space", Element::string, "dhcp4"},
+ { "array", Element::boolean, "false"},
+ { "encapsulate", Element::string, "" }
+};
+
+/// This table defines default values for options in DHCPv6
+const SimpleDefaults ParseConfigTest::OPTION6_DEFAULTS = {
+ { "space", Element::string, "dhcp6"},
+ { "csv-format", Element::boolean, "true"},
+ { "always-send", Element::boolean,"false"}
+};
+
+/// This table defines default values for options in DHCPv4
+const SimpleDefaults ParseConfigTest::OPTION4_DEFAULTS = {
+ { "space", Element::string, "dhcp4"},
+ { "csv-format", Element::boolean, "true"},
+ { "always-send", Element::boolean, "false"}
+};
+
+/// This table defines default values for both DHCPv4 and DHCPv6
+const SimpleDefaults ParseConfigTest::GLOBAL6_DEFAULTS = {
+ { "renew-timer", Element::integer, "900" },
+ { "rebind-timer", Element::integer, "1800" },
+ { "preferred-lifetime", Element::integer, "3600" },
+ { "valid-lifetime", Element::integer, "7200" }
+};
+
+/// @brief Option configuration class
+///
+/// This class handles option-def and option-data which can be recovered
+/// using the toElement() method
+class CfgOptionsTest : public CfgToElement {
+public:
+ /// @brief Constructor
+ ///
+ /// @param cfg the server configuration where to get option-{def,data}
+ CfgOptionsTest(SrvConfigPtr cfg) :
+ cfg_option_def_(cfg->getCfgOptionDef()),
+ cfg_option_(cfg->getCfgOption()) { }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration (a map with
+ /// not empty option-def and option-data lists)
+ ElementPtr toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set option-def
+ ConstElementPtr option_def = cfg_option_def_->toElement();
+ if (!option_def->empty()) {
+ result->set("option-def", option_def);
+ }
+ // Set option-data
+ ConstElementPtr option_data = cfg_option_->toElement();
+ if (!option_data->empty()) {
+ result->set("option-data", option_data);
+ }
+ return (result);
+ }
+
+ /// @brief Run a toElement test (Element version)
+ ///
+ /// Use the runToElementTest template but add defaults to the config
+ ///
+ /// @param family the address family
+ /// @param config the expected result without defaults
+ void runCfgOptionsTest(uint16_t family, ConstElementPtr expected) {
+ ConstElementPtr option_def = expected->get("option-def");
+ if (option_def) {
+ SimpleParser::setListDefaults(option_def,
+ family == AF_INET ?
+ ParseConfigTest::OPTION4_DEF_DEFAULTS :
+ ParseConfigTest::OPTION6_DEF_DEFAULTS);
+ }
+ ConstElementPtr option_data = expected->get("option-data");
+ if (option_data) {
+ SimpleParser::setListDefaults(option_data,
+ family == AF_INET ?
+ ParseConfigTest::OPTION4_DEFAULTS :
+ ParseConfigTest::OPTION6_DEFAULTS);
+ }
+ runToElementTest<CfgOptionsTest>(expected, *this);
+ }
+
+ /// @brief Run a toElement test
+ ///
+ /// Use the runToElementTest template but add defaults to the config
+ ///
+ /// @param family the address family
+ /// @param expected the expected result without defaults
+ void runCfgOptionsTest(uint16_t family, std::string config) {
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config)) << config;
+ runCfgOptionsTest(family, json);
+ }
+
+private:
+ /// @brief Pointer to option definitions configuration.
+ CfgOptionDefPtr cfg_option_def_;
+
+ /// @brief Reference to options (data) configuration.
+ CfgOptionPtr cfg_option_;
+};
+
/// @brief Check basic parsing of option definitions.
///
/// Note that this tests basic operation of the OptionDefinitionListParser and
@@ -483,7 +731,7 @@ TEST_F(ParseConfigTest, basicOptionDefTest) {
" \"name\": \"foo\","
" \"code\": 100,"
" \"type\": \"ipv4-address\","
- " \"array\": False,"
+ " \"array\": false,"
" \"record-types\": \"\","
" \"space\": \"isc\","
" \"encapsulate\": \"\""
@@ -515,6 +763,10 @@ TEST_F(ParseConfigTest, basicOptionDefTest) {
// but the values should be equal.
EXPECT_TRUE(def_libdhcp != def);
EXPECT_TRUE(*def_libdhcp == *def);
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
}
/// @brief Check minimal parsing of option definitions.
@@ -548,6 +800,10 @@ TEST_F(ParseConfigTest, minimalOptionDefTest) {
EXPECT_FALSE(def->getArrayType());
EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
}
/// @brief Check parsing of option definitions using default dhcp6 space.
@@ -566,7 +822,7 @@ TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) {
"}";
// Verify that the configuration string parses.
- int rcode = parseConfiguration(config);
+ int rcode = parseConfiguration(config, true);
ASSERT_EQ(0, rcode);
@@ -581,6 +837,10 @@ TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) {
EXPECT_FALSE(def->getArrayType());
EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
}
/// @brief Check basic parsing of options.
@@ -604,7 +864,8 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
" \"space\": \"isc\","
" \"code\": 100,"
" \"data\": \"192.0.2.0\","
- " \"csv-format\": True"
+ " \"csv-format\": true,"
+ " \"always-send\": false"
" } ]"
"}";
@@ -620,6 +881,10 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";
EXPECT_EQ(val, opt_ptr->toText());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
}
/// @brief Check minimal parsing of options.
@@ -654,6 +919,12 @@ TEST_F(ParseConfigTest, minimalOptionDataTest) {
std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";
EXPECT_EQ(val, opt_ptr->toText());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(100));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
}
/// @brief Check parsing of options with escape characters.
@@ -665,7 +936,7 @@ TEST_F(ParseConfigTest, minimalOptionDataTest) {
/// has the actual character (e.g. an actual backslash, not double backslash).
TEST_F(ParseConfigTest, escapedOptionDataTest) {
- parser_context_->universe_ = Option::V4;
+ family_ = AF_INET;
// We need to use double escapes here. The first backslash will
// be consumed by C++ preprocessor, so the actual string will
@@ -677,7 +948,6 @@ TEST_F(ParseConfigTest, escapedOptionDataTest) {
" \"data\": \"\\\\SMSBoot\\\\x64\\\\wdsnbp.com\""
" } ]"
"}";
- std::cout << config << std::endl;
// Verify that the configuration string parses.
int rcode = parseConfiguration(config);
@@ -698,6 +968,12 @@ TEST_F(ParseConfigTest, escapedOptionDataTest) {
EXPECT_EQ(Option::OPTION4_HDR_LEN + 23, buf.getLength());
EXPECT_TRUE(0 == memcmp(buf.getData(), exp, 25));
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(DHO_BOOT_FILE_NAME));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
}
// This test checks behavior of the configuration parser for option data
@@ -715,7 +991,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
// The default universe is V6. We need to change it to use dhcp4 option
// space.
- parser_context_->universe_ = Option::V4;
+ family_ = AF_INET;
int rcode = 0;
ASSERT_NO_THROW(rcode = parseConfiguration(config));
ASSERT_EQ(0, rcode);
@@ -726,6 +1002,9 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
ASSERT_TRUE(addr_opt);
EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+
// Explicitly enable csv-format.
CfgMgr::instance().clear();
config =
@@ -733,7 +1012,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
" \"name\": \"swap-server\","
" \"space\": \"dhcp4\","
" \"code\": 16,"
- " \"csv-format\": True,"
+ " \"csv-format\": true,"
" \"data\": \"192.0.2.0\""
" } ]"
"}";
@@ -746,6 +1025,8 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
ASSERT_TRUE(addr_opt);
EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
+ // To make runToElementTest to work the csv-format must be removed...
+
// Explicitly disable csv-format and use hex instead.
CfgMgr::instance().clear();
config =
@@ -753,7 +1034,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
" \"name\": \"swap-server\","
" \"space\": \"dhcp4\","
" \"code\": 16,"
- " \"csv-format\": False,"
+ " \"csv-format\": false,"
" \"data\": \"C0000200\""
" } ]"
"}";
@@ -765,50 +1046,9 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
ASSERT_TRUE(addr_opt);
EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
-}
-// This test verifies that definitions of standard encapsulated
-// options can be used.
-TEST_F(ParseConfigTest, encapsulatedOptionData) {
- std::string config =
- "{ \"option-data\": [ {"
- " \"space\": \"s46-cont-mape-options\","
- " \"name\": \"s46-rule\","
- " \"data\": \"1, 0, 24, 192.0.2.0, 2001:db8:1::/64\""
- " } ]"
- "}";
-
- // Make sure that we're using correct universe.
- parser_context_->universe_ = Option::V6;
- int rcode = 0;
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
- ASSERT_EQ(0, rcode);
-
- // Verify that the option data is correct.
- OptionCustomPtr s46_rule = boost::dynamic_pointer_cast<OptionCustom>
- (getOptionPtr(MAPE_V6_OPTION_SPACE, D6O_S46_RULE));
- ASSERT_TRUE(s46_rule);
-
- uint8_t flags;
- uint8_t ea_len;
- uint8_t prefix4_len;
- IOAddress ipv4_prefix(IOAddress::IPV4_ZERO_ADDRESS());
- PrefixTuple ipv6_prefix(PrefixLen(0), IOAddress::IPV6_ZERO_ADDRESS());;
-
- ASSERT_NO_THROW({
- flags = s46_rule->readInteger<uint8_t>(0);
- ea_len = s46_rule->readInteger<uint8_t>(1);
- prefix4_len = s46_rule->readInteger<uint8_t>(2);
- ipv4_prefix = s46_rule->readAddress(3);
- ipv6_prefix = s46_rule->readPrefix(4);
- });
-
- EXPECT_EQ(1, flags);
- EXPECT_EQ(0, ea_len);
- EXPECT_EQ(24, prefix4_len);
- EXPECT_EQ("192.0.2.0", ipv4_prefix.toText());
- EXPECT_EQ(64, ipv6_prefix.first.asUnsigned());
- EXPECT_EQ("2001:db8:1::", ipv6_prefix.second.toText());
+ CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+ cfg2.runCfgOptionsTest(family_, config);
}
// This test checks behavior of the configuration parser for option data
@@ -828,7 +1068,7 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
" } ]"
"}";
int rcode = 0;
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
EXPECT_NE(0, rcode);
CfgMgr::instance().clear();
@@ -840,11 +1080,11 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
" \"name\": \"foo-name\","
" \"space\": \"dhcp6\","
" \"code\": 25000,"
- " \"csv-format\": True,"
+ " \"csv-format\": true,"
" \"data\": \"0\""
" } ]"
"}";
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
EXPECT_NE(0, rcode);
CfgMgr::instance().clear();
@@ -855,17 +1095,24 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
" \"name\": \"foo-name\","
" \"space\": \"dhcp6\","
" \"code\": 25000,"
- " \"csv-format\": False,"
+ " \"csv-format\": false,"
" \"data\": \"0\""
" } ]"
"}";
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
ASSERT_EQ(0, rcode);
OptionPtr opt = getOptionPtr(DHCP6_OPTION_SPACE, 25000);
ASSERT_TRUE(opt);
ASSERT_EQ(1, opt->getData().size());
EXPECT_EQ(0, opt->getData()[0]);
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->remove("name");
+ opt_data->set("data", Element::create(std::string("00")));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+
CfgMgr::instance().clear();
// When csv-format is not specified, the parser will check if the definition
// exists or not. Since there is no definition, the parser will accept the
@@ -875,10 +1122,11 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
" \"name\": \"foo-name\","
" \"space\": \"dhcp6\","
" \"code\": 25000,"
+ " \"csv-format\": false,"
" \"data\": \"123456\""
" } ]"
"}";
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
EXPECT_EQ(0, rcode);
opt = getOptionPtr(DHCP6_OPTION_SPACE, 25000);
ASSERT_TRUE(opt);
@@ -886,6 +1134,12 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
EXPECT_EQ(0x12, opt->getData()[0]);
EXPECT_EQ(0x34, opt->getData()[1]);
EXPECT_EQ(0x56, opt->getData()[2]);
+
+ expected = Element::fromJSON(config);
+ opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->remove("name");
+ CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+ cfg2.runCfgOptionsTest(family_, expected);
}
// This test verifies that the option name is not mandatory, if the option
@@ -899,13 +1153,19 @@ TEST_F(ParseConfigTest, optionDataNoName) {
" } ]"
"}";
int rcode = 0;
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
EXPECT_EQ(0, rcode);
Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
ASSERT_TRUE(opt);
ASSERT_EQ(1, opt->getAddresses().size());
EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("name", Element::create(std::string("dns-servers")));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
}
// This test verifies that the option code is not mandatory, if the option
@@ -919,13 +1179,19 @@ TEST_F(ParseConfigTest, optionDataNoCode) {
" } ]"
"}";
int rcode = 0;
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
EXPECT_EQ(0, rcode);
Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
ASSERT_TRUE(opt);
ASSERT_EQ(1, opt->getAddresses().size());
EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(D6O_NAME_SERVERS));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
}
// This test verifies that the option data configuration with a minimal
@@ -938,7 +1204,7 @@ TEST_F(ParseConfigTest, optionDataMinimal) {
" } ]"
"}";
int rcode = 0;
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
EXPECT_EQ(0, rcode);
Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
@@ -946,6 +1212,13 @@ TEST_F(ParseConfigTest, optionDataMinimal) {
ASSERT_EQ(1, opt->getAddresses().size());
EXPECT_EQ( "2001:db8:1::10", opt->getAddresses()[0].toText());
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(D6O_NAME_SERVERS));
+ opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+
CfgMgr::instance().clear();
// This time using an option code.
config =
@@ -955,18 +1228,25 @@ TEST_F(ParseConfigTest, optionDataMinimal) {
" } ]"
"}";
rcode = 0;
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
EXPECT_EQ(0, rcode);
opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE,
23));
ASSERT_TRUE(opt);
ASSERT_EQ(1, opt->getAddresses().size());
EXPECT_EQ( "2001:db8:1::20", opt->getAddresses()[0].toText());
+
+ expected = Element::fromJSON(config);
+ opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("name", Element::create(std::string("dns-servers")));
+ opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+ cfg2.runCfgOptionsTest(family_, expected);
}
// This test verifies that the option data configuration with a minimal
// set of parameters works as expected when option definition is
-// created in the configruation file.
+// created in the configuration file.
TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
// Configuration string.
std::string config =
@@ -974,7 +1254,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
" \"name\": \"foo-name\","
" \"code\": 2345,"
" \"type\": \"ipv6-address\","
- " \"array\": True,"
+ " \"array\": true,"
" \"space\": \"dhcp6\""
" } ],"
" \"option-data\": [ {"
@@ -984,7 +1264,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
"}";
int rcode = 0;
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
EXPECT_EQ(0, rcode);
Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 2345));
@@ -993,6 +1273,13 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(2345));
+ opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+
CfgMgr::instance().clear();
// Do the same test but now use an option code.
config =
@@ -1000,7 +1287,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
" \"name\": \"foo-name\","
" \"code\": 2345,"
" \"type\": \"ipv6-address\","
- " \"array\": True,"
+ " \"array\": true,"
" \"space\": \"dhcp6\""
" } ],"
" \"option-data\": [ {"
@@ -1010,7 +1297,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
"}";
rcode = 0;
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
EXPECT_EQ(0, rcode);
opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE,
2345));
@@ -1019,6 +1306,12 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());
+ expected = Element::fromJSON(config);
+ opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("name", Element::create(std::string("foo-name")));
+ opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+ cfg2.runCfgOptionsTest(family_, expected);
}
// This test verifies an empty option data configuration is supported.
@@ -1031,16 +1324,25 @@ TEST_F(ParseConfigTest, emptyOptionData) {
"}";
int rcode = 0;
- ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
EXPECT_EQ(0, rcode);
const Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, D6O_DHCPV4_O_DHCPV6_SERVER));
ASSERT_TRUE(opt);
ASSERT_EQ(0, opt->getAddresses().size());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(D6O_DHCPV4_O_DHCPV6_SERVER));
+ opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ opt_data->set("csv-format", Element::create(false));
+ opt_data->set("data", Element::create(std::string("")));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
}
// This test verifies an option data without suboptions is supported
-TEST_F(ParseConfigTest, optionDataNoSubOpion) {
+TEST_F(ParseConfigTest, optionDataNoSubOption) {
// Configuration string.
const std::string config =
"{ \"option-data\": [ {"
@@ -1050,13 +1352,60 @@ TEST_F(ParseConfigTest, optionDataNoSubOpion) {
// The default universe is V6. We need to change it to use dhcp4 option
// space.
- parser_context_->universe_ = Option::V4;
+ family_ = AF_INET;
int rcode = 0;
ASSERT_NO_THROW(rcode = parseConfiguration(config));
EXPECT_EQ(0, rcode);
const OptionPtr opt = getOptionPtr(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
ASSERT_TRUE(opt);
ASSERT_EQ(0, opt->getOptions().size());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(DHO_VENDOR_ENCAPSULATED_OPTIONS));
+ opt_data->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
+ opt_data->set("csv-format", Element::create(false));
+ opt_data->set("data", Element::create(std::string("")));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+}
+
+// This tests option-data in CSV format and embedded commas.
+TEST_F(ParseConfigTest, commaCSVFormatOptionData) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"csv-format\": true,"
+ " \"code\": 41,"
+ " \"data\": \"EST5EDT4\\\\,M3.2.0/02:00\\\\,M11.1.0/02:00\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config, true);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt = getOptionPtr(DHCP6_OPTION_SPACE, 41);
+ ASSERT_TRUE(opt);
+
+ // Get the option as an option string.
+ OptionStringPtr opt_str = boost::dynamic_pointer_cast<OptionString>(opt);
+ ASSERT_TRUE(opt_str);
+
+
+ // Verify that the option data is correct.
+ string val = "EST5EDT4,M3.2.0/02:00,M11.1.0/02:00";
+ EXPECT_EQ(val, opt_str->getValue());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->remove("csv-format");
+ opt_data->set("name", Element::create(std::string("new-posix-timezone")));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
}
/// The next set of tests check basic operation of the HooksLibrariesParser.
@@ -1121,11 +1470,17 @@ TEST_F(ParseConfigTest, noHooksLibraries) {
const int rcode = parseConfiguration(config);
ASSERT_TRUE(rcode == 0) << error_text_;
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
// Check that the parser recorded nothing.
- isc::hooks::HookLibsCollection libraries;
- bool changed;
- hooks_libraries_parser_->getLibraries(libraries, changed);
- EXPECT_FALSE(changed);
+ isc::hooks::HookLibsCollection libraries = getLibraries();
EXPECT_TRUE(libraries.empty());
// Check that there are still no libraries loaded.
@@ -1146,11 +1501,17 @@ TEST_F(ParseConfigTest, oneHooksLibrary) {
const int rcode = parseConfiguration(config);
ASSERT_TRUE(rcode == 0) << error_text_;
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
// Check that the parser recorded a single library.
- HookLibsCollection libraries;
- bool changed;
- hooks_libraries_parser_->getLibraries(libraries, changed);
- EXPECT_TRUE(changed);
+ isc::hooks::HookLibsCollection libraries = getLibraries();
ASSERT_EQ(1, libraries.size());
EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
@@ -1174,11 +1535,17 @@ TEST_F(ParseConfigTest, twoHooksLibraries) {
const int rcode = parseConfiguration(config);
ASSERT_TRUE(rcode == 0) << error_text_;
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
// Check that the parser recorded two libraries in the expected order.
- HookLibsCollection libraries;
- bool changed;
- hooks_libraries_parser_->getLibraries(libraries, changed);
- EXPECT_TRUE(changed);
+ isc::hooks::HookLibsCollection libraries = getLibraries();
ASSERT_EQ(2, libraries.size());
EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[1].first);
@@ -1205,6 +1572,15 @@ TEST_F(ParseConfigTest, reconfigureSameHooksLibraries) {
int rcode = parseConfiguration(config);
ASSERT_TRUE(rcode == 0) << error_text_;
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
// The previous test shows that the parser correctly recorded the two
// libraries and that they loaded correctly.
@@ -1213,12 +1589,12 @@ TEST_F(ParseConfigTest, reconfigureSameHooksLibraries) {
ASSERT_TRUE(rcode == 0) << error_text_;
// The list has not changed between the two parse operations. However,
- // the paramters (or the files they could point to) could have
+ // the parameters (or the files they could point to) could have
// changed, so the libraries are reloaded anyway.
- HookLibsCollection libraries;
- bool changed;
- hooks_libraries_parser_->getLibraries(libraries, changed);
- EXPECT_TRUE(changed);
+ const HooksConfig& cfg2 =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg2);
+ isc::hooks::HookLibsCollection libraries = getLibraries();
ASSERT_EQ(2, libraries.size());
EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[1].first);
@@ -1256,10 +1632,7 @@ TEST_F(ParseConfigTest, reconfigureReverseHooksLibraries) {
ASSERT_TRUE(rcode == 0) << error_text_;
// The list has changed, and this is what we should see.
- HookLibsCollection libraries;
- bool changed;
- hooks_libraries_parser_->getLibraries(libraries, changed);
- EXPECT_TRUE(changed);
+ isc::hooks::HookLibsCollection libraries = getLibraries();
ASSERT_EQ(2, libraries.size());
EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[0].first);
EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[1].first);
@@ -1294,11 +1667,17 @@ TEST_F(ParseConfigTest, reconfigureZeroHooksLibraries) {
rcode = parseConfiguration(config);
ASSERT_TRUE(rcode == 0) << error_text_;
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
// The list has changed, and this is what we should see.
- HookLibsCollection libraries;
- bool changed;
- hooks_libraries_parser_->getLibraries(libraries, changed);
- EXPECT_TRUE(changed);
+ isc::hooks::HookLibsCollection libraries = getLibraries();
EXPECT_TRUE(libraries.empty());
// Check that no libraries are currently loaded
@@ -1329,10 +1708,7 @@ TEST_F(ParseConfigTest, invalidHooksLibraries) {
// Check that the parser recorded the names but, as they were in error,
// does not flag them as changed.
- HookLibsCollection libraries;
- bool changed;
- hooks_libraries_parser_->getLibraries(libraries, changed);
- EXPECT_FALSE(changed);
+ isc::hooks::HookLibsCollection libraries = getLibraries();
ASSERT_EQ(3, libraries.size());
EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
EXPECT_EQ(NOT_PRESENT_LIBRARY, libraries[1].first);
@@ -1373,10 +1749,7 @@ TEST_F(ParseConfigTest, reconfigureInvalidHooksLibraries) {
// Check that the parser recorded the names but, as the library set was
// incorrect, did not mark the configuration as changed.
- HookLibsCollection libraries;
- bool changed;
- hooks_libraries_parser_->getLibraries(libraries, changed);
- EXPECT_FALSE(changed);
+ isc::hooks::HookLibsCollection libraries = getLibraries();
ASSERT_EQ(3, libraries.size());
EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
EXPECT_EQ(NOT_PRESENT_LIBRARY, libraries[1].first);
@@ -1479,11 +1852,17 @@ TEST_F(ParseConfigTest, HooksLibrariesParameters) {
const int rcode = parseConfiguration(config);
ASSERT_EQ(0, rcode);
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
// Check that the parser recorded the names.
- HookLibsCollection libraries;
- bool changed = false;
- hooks_libraries_parser_->getLibraries(libraries, changed);
- EXPECT_TRUE(changed);
+ isc::hooks::HookLibsCollection libraries = getLibraries();
ASSERT_EQ(3, libraries.size());
EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[1].first);
@@ -1574,6 +1953,12 @@ TEST_F(ParseConfigTest, validD2Config) {
EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected = Element::fromJSON(config_str)->get("dhcp-ddns"));
+ ASSERT_TRUE(expected);
+ runToElementTest<D2ClientConfig>(expected, *d2_client_config);
+
// Another valid Configuration string.
// This one is disabled, has IPV6 server ip, control flags false,
// empty prefix/suffix
@@ -1618,6 +2003,10 @@ TEST_F(ParseConfigTest, validD2Config) {
EXPECT_EQ(D2ClientConfig::RCM_NEVER, d2_client_config->getReplaceClientNameMode());
EXPECT_EQ("", d2_client_config->getGeneratedPrefix());
EXPECT_EQ("", d2_client_config->getQualifyingSuffix());
+
+ ASSERT_NO_THROW(expected = Element::fromJSON(config_str2)->get("dhcp-ddns"));
+ ASSERT_TRUE(expected);
+ runToElementTest<D2ClientConfig>(expected, *d2_client_config);
}
/// @brief Checks that D2 client can be configured with enable flag of
@@ -1701,11 +2090,6 @@ TEST_F(ParseConfigTest, parserDefaultsD2Config) {
/// @brief Check various invalid D2 client configurations.
TEST_F(ParseConfigTest, invalidD2Config) {
std::string invalid_configs[] = {
- // Must supply at least enable-updates
- "{ \"dhcp-ddns\" :"
- " {"
- " }"
- "}",
// Must supply qualifying-suffix when updates are enabled
"{ \"dhcp-ddns\" :"
" {"
@@ -1877,410 +2261,6 @@ TEST_F(ParseConfigTest, invalidD2Config) {
}
}
-/// @brief DHCP Configuration Parser Context test fixture.
-class ParserContextTest : public ::testing::Test {
-public:
- /// @brief Constructor
- ParserContextTest() { }
-
- /// @brief Check that the storages of the specific type hold the
- /// same value.
- ///
- /// This function assumes that the ref_values storage holds parameter
- /// called 'foo'.
- ///
- /// @param ref_values A storage holding reference value. In the typical
- /// case it is a storage held in the original context, which is assigned
- /// to another context.
- /// @param values A storage holding value to be checked.
- /// @tparam ContainerType A type of the storage.
- template<typename ContainerType>
- void checkValueEq(const boost::shared_ptr<ContainerType>& ref_values,
- const boost::shared_ptr<ContainerType>& values) {
- ASSERT_NO_THROW(values->getParam("foo"));
- EXPECT_EQ(ref_values->getParam("foo"), values->getParam("foo"));
- }
-
- /// @brief Check that the storages of the specific type hold the same
- /// position of the parameter.
- ///
- /// @param name A name of the parameter to check.
- /// @param ref_values A storage holding reference position. In the typical
- /// case it is a storage held in the original context, which is assigned
- /// to another context.
- /// @param values A storage holding position to be checked.
- /// @tparam ContainerType A type of the storage.
- template<typename ContainerType>
- void checkPositionEq(const std::string& name,
- const boost::shared_ptr<ContainerType>& ref_values,
- const boost::shared_ptr<ContainerType>& values) {
- // Verify that the position is correct.
- EXPECT_EQ(ref_values->getPosition(name).line_,
- values->getPosition(name).line_);
-
- EXPECT_EQ(ref_values->getPosition(name).pos_,
- values->getPosition(name).pos_);
-
- EXPECT_EQ(ref_values->getPosition(name).file_,
- values->getPosition(name).file_);
- }
-
- /// @brief Check that the storages of the specific type hold different
- /// value.
- ///
- /// This function assumes that the ref_values storage holds exactly
- /// one parameter called 'foo'.
- ///
- /// @param ref_values A storage holding reference value. In the typical
- /// case it is a storage held in the original context, which is assigned
- /// to another context.
- /// @param values A storage holding value to be checked.
- /// @tparam ContainerType A type of the storage.
- /// @tparam ValueType A type of the value in the container.
- template<typename ContainerType>
- void checkValueNeq(const boost::shared_ptr<ContainerType>& ref_values,
- const boost::shared_ptr<ContainerType>& values) {
- ASSERT_NO_THROW(values->getParam("foo"));
- EXPECT_NE(ref_values->getParam("foo"), values->getParam("foo"));
- }
-
- /// @brief Check that the storages of the specific type hold different
- /// position.
- ///
- /// @param name A name of the parameter to be checked.
- /// @param ref_values A storage holding reference position. In the typical
- /// case it is a storage held in the original context, which is assigned
- /// to another context.
- /// @param values A storage holding position to be checked.
- /// @tparam ContainerType A type of the storage.
- template<typename ContainerType>
- void checkPositionNeq(const std::string& name,
- const boost::shared_ptr<ContainerType>& ref_values,
- const boost::shared_ptr<ContainerType>& values) {
- // At least one of the position fields must be different.
- EXPECT_TRUE((ref_values->getPosition(name).line_ !=
- values->getPosition(name).line_) ||
- (ref_values->getPosition(name).pos_ !=
- values->getPosition(name).pos_) ||
- (ref_values->getPosition(name).file_ !=
- values->getPosition(name).file_));
- }
-
- /// @brief Test copy constructor or assignment operator when values
- /// being copied are NULL.
- ///
- /// @param copy Indicates that copy constructor should be tested
- /// (if true), or assignment operator (if false).
- void testCopyAssignmentNull(const bool copy) {
- ParserContext ctx(Option::V6);
- // Release all pointers in the context.
- ctx.boolean_values_.reset();
- ctx.uint32_values_.reset();
- ctx.string_values_.reset();
- ctx.hooks_libraries_.reset();
-
- // Even if the fields of the context are NULL, it should get
- // copied.
- ParserContextPtr ctx_new(new ParserContext(Option::V6));
- if (copy) {
- ASSERT_NO_THROW(ctx_new.reset(new ParserContext(ctx)));
- } else {
- *ctx_new = ctx;
- }
-
- // The resulting context has its fields equal to NULL.
- EXPECT_FALSE(ctx_new->boolean_values_);
- EXPECT_FALSE(ctx_new->uint32_values_);
- EXPECT_FALSE(ctx_new->string_values_);
- EXPECT_FALSE(ctx_new->hooks_libraries_);
-
- }
-
- /// @brief Test copy constructor or assignment operator.
- ///
- /// @param copy Indicates that copy constructor should be tested (if true),
- /// or assignment operator (if false).
- void testCopyAssignment(const bool copy) {
- // Create new context. It will be later copied/assigned to another
- // context.
- ParserContext ctx(Option::V6);
-
- // Set boolean parameter 'foo'.
- ASSERT_TRUE(ctx.boolean_values_);
- ctx.boolean_values_->setParam("foo", true,
- Element::Position("kea.conf", 123, 234));
-
- // Set various parameters to test that position is copied between
- // contexts.
- ctx.boolean_values_->setParam("pos0", true,
- Element::Position("kea.conf", 1, 2));
- ctx.boolean_values_->setParam("pos1", true,
- Element::Position("kea.conf", 10, 20));
- ctx.boolean_values_->setParam("pos2", true,
- Element::Position("kea.conf", 100, 200));
-
- // Set uint32 parameter 'foo'.
- ASSERT_TRUE(ctx.uint32_values_);
- ctx.uint32_values_->setParam("foo", 123,
- Element::Position("kea.conf", 123, 234));
-
- // Set various parameters to test that position is copied between
- // contexts.
- ctx.uint32_values_->setParam("pos0", 123,
- Element::Position("kea.conf", 1, 2));
- ctx.uint32_values_->setParam("pos1", 123,
- Element::Position("kea.conf", 10, 20));
- ctx.uint32_values_->setParam("pos2", 123,
- Element::Position("kea.conf", 100, 200));
-
- // Ser string parameter 'foo'.
- ASSERT_TRUE(ctx.string_values_);
- ctx.string_values_->setParam("foo", "some string",
- Element::Position("kea.conf", 123, 234));
-
- // Set various parameters to test that position is copied between
- // contexts.
- ctx.string_values_->setParam("pos0", "some string",
- Element::Position("kea.conf", 1, 2));
- ctx.string_values_->setParam("pos1", "some string",
- Element::Position("kea.conf", 10, 20));
- ctx.string_values_->setParam("pos2", "some string",
- Element::Position("kea.conf", 100, 200));
-
-
- // Allocate container for hooks libraries and add one library name.
- ctx.hooks_libraries_.reset(new std::vector<HookLibInfo>());
- ctx.hooks_libraries_->push_back(make_pair("library1", ConstElementPtr()));
-
- // We will use ctx_new to assign another context to it or copy
- // construct.
- ParserContextPtr ctx_new(new ParserContext(Option::V4));;
- if (copy) {
- ctx_new.reset(new ParserContext(ctx));
- } else {
- *ctx_new = ctx;
- }
-
- // New context has the same boolean value.
- ASSERT_TRUE(ctx_new->boolean_values_);
- {
- SCOPED_TRACE("Check that boolean values are equal in both"
- " contexts");
- checkValueEq(ctx.boolean_values_, ctx_new->boolean_values_);
- }
-
- // New context has the same boolean values' positions.
- {
- SCOPED_TRACE("Check that positions of boolean values are equal"
- " in both contexts");
- checkPositionEq("pos0", ctx.boolean_values_,
- ctx_new->boolean_values_);
- checkPositionEq("pos1", ctx.boolean_values_,
- ctx_new->boolean_values_);
- checkPositionEq("pos2", ctx.boolean_values_,
- ctx_new->boolean_values_);
- }
-
- // New context has the same uint32 value.
- ASSERT_TRUE(ctx_new->uint32_values_);
- {
- SCOPED_TRACE("Check that uint32_t values are equal in both"
- " contexts");
- checkValueEq(ctx.uint32_values_, ctx_new->uint32_values_);
- }
-
- // New context has the same uint32 values' positions.
- {
- SCOPED_TRACE("Check that positions of uint32 values are equal"
- " in both contexts");
- checkPositionEq("pos0", ctx.uint32_values_,
- ctx_new->uint32_values_);
- checkPositionEq("pos1", ctx.uint32_values_,
- ctx_new->uint32_values_);
- checkPositionEq("pos2", ctx.uint32_values_,
- ctx_new->uint32_values_);
- }
-
- // New context has the same uint32 value position.
- {
- SCOPED_TRACE("Check that positions of uint32_t values are equal"
- " in both contexts");
- checkPositionEq("foo", ctx.uint32_values_, ctx_new->uint32_values_);
- }
-
- // New context has the same string value.
- ASSERT_TRUE(ctx_new->string_values_);
- {
- SCOPED_TRACE("Check that string values are equal in both contexts");
- checkValueEq(ctx.string_values_, ctx_new->string_values_);
- }
-
- // New context has the same string values' positions.
- {
- SCOPED_TRACE("Check that positions of string values are equal"
- " in both contexts");
- checkPositionEq("pos0", ctx.string_values_,
- ctx_new->string_values_);
- checkPositionEq("pos1", ctx.string_values_,
- ctx_new->string_values_);
- checkPositionEq("pos2", ctx.string_values_,
- ctx_new->string_values_);
- }
-
- // New context has the same hooks library.
- ASSERT_TRUE(ctx_new->hooks_libraries_);
- {
- ASSERT_EQ(1, ctx_new->hooks_libraries_->size());
- EXPECT_EQ("library1", (*ctx_new->hooks_libraries_)[0].first);
- }
-
- // New context has the same universe.
- EXPECT_EQ(ctx.universe_, ctx_new->universe_);
-
- // Change the value of the boolean parameter. This should not affect the
- // corresponding value in the new context.
- {
- SCOPED_TRACE("Check that boolean value isn't changed when original"
- " value and position is changed");
- ctx.boolean_values_->setParam("foo", false,
- Element::Position("kea.conf",
- 12, 10));
- checkValueNeq(ctx.boolean_values_, ctx_new->boolean_values_);
-
- }
-
- {
- SCOPED_TRACE("Check that positions of the boolean parameters aren't"
- " changed when the corresponding positions in the"
- " original context are changed");
- // Modify file name.
- ctx.boolean_values_->setParam("pos0", false,
- Element::Position("foo.conf",
- 1, 2));
- checkPositionNeq("pos0", ctx.boolean_values_,
- ctx_new->boolean_values_);
- // Modify line number.
- ctx.boolean_values_->setParam("pos1", false,
- Element::Position("kea.conf",
- 11, 20));
- checkPositionNeq("pos1", ctx.boolean_values_,
- ctx_new->boolean_values_);
- // Modify position within a line.
- ctx.boolean_values_->setParam("pos2", false,
- Element::Position("kea.conf",
- 101, 201));
- checkPositionNeq("pos2", ctx.boolean_values_,
- ctx_new->boolean_values_);
-
- }
-
- // Change the value of the uint32_t parameter. This should not affect
- // the corresponding value in the new context.
- {
- SCOPED_TRACE("Check that uint32_t value isn't changed when original"
- " value and position is changed");
- ctx.uint32_values_->setParam("foo", 987,
- Element::Position("kea.conf", 10, 11));
- checkValueNeq(ctx.uint32_values_, ctx_new->uint32_values_);
- }
-
- {
- SCOPED_TRACE("Check that positions of the uint32 parameters aren't"
- " changed when the corresponding positions in the"
- " original context are changed");
- // Modify file name.
- ctx.uint32_values_->setParam("pos0", 123,
- Element::Position("foo.conf", 1, 2));
- checkPositionNeq("pos0", ctx.uint32_values_,
- ctx_new->uint32_values_);
- // Modify line number.
- ctx.uint32_values_->setParam("pos1", 123,
- Element::Position("kea.conf",
- 11, 20));
- checkPositionNeq("pos1", ctx.uint32_values_,
- ctx_new->uint32_values_);
- // Modify position within a line.
- ctx.uint32_values_->setParam("pos2", 123,
- Element::Position("kea.conf",
- 101, 201));
- checkPositionNeq("pos2", ctx.uint32_values_,
- ctx_new->uint32_values_);
-
- }
-
- // Change the value of the string parameter. This should not affect the
- // corresponding value in the new context.
- {
- SCOPED_TRACE("Check that string value isn't changed when original"
- " value and position is changed");
- ctx.string_values_->setParam("foo", "different string",
- Element::Position("kea.conf", 10, 11));
- checkValueNeq(ctx.string_values_, ctx_new->string_values_);
- }
-
- {
- SCOPED_TRACE("Check that positions of the string parameters aren't"
- " changed when the corresponding positions in the"
- " original context are changed");
- // Modify file name.
- ctx.string_values_->setParam("pos0", "some string",
- Element::Position("foo.conf", 1, 2));
- checkPositionNeq("pos0", ctx.string_values_,
- ctx_new->string_values_);
- // Modify line number.
- ctx.string_values_->setParam("pos1", "some string",
- Element::Position("kea.conf",
- 11, 20));
- checkPositionNeq("pos1", ctx.string_values_,
- ctx_new->string_values_);
- // Modify position within a line.
- ctx.string_values_->setParam("pos2", "some string",
- Element::Position("kea.conf",
- 101, 201));
- checkPositionNeq("pos2", ctx.string_values_,
- ctx_new->string_values_);
-
- }
-
- // Change the list of libraries. this should not affect the list in the
- // new context.
- ctx.hooks_libraries_->clear();
- ctx.hooks_libraries_->push_back(make_pair("library2", ConstElementPtr()));
- ASSERT_EQ(1, ctx_new->hooks_libraries_->size());
- EXPECT_EQ("library1", (*ctx_new->hooks_libraries_)[0].first);
-
- // Change the universe. This should not affect the universe value in the
- // new context.
- ctx.universe_ = Option::V4;
- EXPECT_EQ(Option::V6, ctx_new->universe_);
-
- }
-
-};
-
-// Check that the assignment operator of the ParserContext class copies all
-// fields correctly.
-TEST_F(ParserContextTest, assignment) {
- testCopyAssignment(false);
-}
-
-// Check that the assignment operator of the ParserContext class copies all
-// fields correctly when these fields are NULL.
-TEST_F(ParserContextTest, assignmentNull) {
- testCopyAssignmentNull(false);
-}
-
-// Check that the context is copy constructed correctly.
-TEST_F(ParserContextTest, copyConstruct) {
- testCopyAssignment(true);
-}
-
-// Check that the context is copy constructed correctly, when context fields
-// are NULL.
-TEST_F(ParserContextTest, copyConstructNull) {
- testCopyAssignmentNull(true);
-}
-
/// @brief Checks that a valid relay info structure for IPv4 can be handled
TEST_F(ParseConfigTest, validRelayInfo4) {
@@ -2291,6 +2271,19 @@ TEST_F(ParseConfigTest, validRelayInfo4) {
" }";
ElementPtr json = Element::fromJSON(config_str);
+ // We need to set the default ip-address to something.
+ Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("0.0.0.0")));
+
+ RelayInfoParser parser(Option::V4);
+
+ // Subnet4 parser will pass 0.0.0.0 to the RelayInfoParser
+ EXPECT_NO_THROW(parser.parse(result, json));
+ EXPECT_EQ("192.0.2.1", result->addr_.toText());
+}
+
+/// @brief Checks that a bogus relay info structure for IPv4 is rejected.
+TEST_F(ParseConfigTest, bogusRelayInfo4) {
+
// Invalid config (wrong family type of the ip-address field)
std::string config_str_bogus1 =
" {"
@@ -2305,24 +2298,25 @@ TEST_F(ParseConfigTest, validRelayInfo4) {
" }";
ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2);
- // We need to set the default ip-address to something.
- Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("0.0.0.0")));
+ // Invalid config (ip-address is mandatory)
+ std::string config_str_bogus3 =
+ " {"
+ " }";
+ ElementPtr json_bogus3 = Element::fromJSON(config_str_bogus3);
- boost::shared_ptr<RelayInfoParser> parser;
+ // We need to set the default ip-address to something.
+ Subnet::RelayInfoPtr result(new Subnet::RelayInfo(IOAddress::IPV4_ZERO_ADDRESS()));
- // Subnet4 parser will pass 0.0.0.0 to the RelayInfoParser
- EXPECT_NO_THROW(parser.reset(new RelayInfoParser("ignored", result,
- Option::V4)));
- EXPECT_NO_THROW(parser->build(json));
- EXPECT_NO_THROW(parser->commit());
+ RelayInfoParser parser(Option::V4);
- EXPECT_EQ("192.0.2.1", result->addr_.toText());
+ // wrong family type
+ EXPECT_THROW(parser.parse(result, json_bogus1), DhcpConfigError);
- // Let's check negative scenario (wrong family type)
- EXPECT_THROW(parser->build(json_bogus1), DhcpConfigError);
+ // Too large byte values in pseudo-IPv4 addr
+ EXPECT_THROW(parser.parse(result, json_bogus2), DhcpConfigError);
- // Let's check negative scenario (too large byte values in pseudo-IPv4 addr)
- EXPECT_THROW(parser->build(json_bogus2), DhcpConfigError);
+ // Mandatory ip-address is missing. What a pity.
+ EXPECT_THROW(parser.parse(result, json_bogus2), DhcpConfigError);
}
/// @brief Checks that a valid relay info structure for IPv6 can be handled
@@ -2335,6 +2329,18 @@ TEST_F(ParseConfigTest, validRelayInfo6) {
" }";
ElementPtr json = Element::fromJSON(config_str);
+ // We need to set the default ip-address to something.
+ Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("::")));
+
+ RelayInfoParser parser(Option::V6);
+ // Subnet4 parser will pass :: to the RelayInfoParser
+ EXPECT_NO_THROW(parser.parse(result, json));
+ EXPECT_EQ("2001:db8::1", result->addr_.toText());
+}
+
+/// @brief Checks that a valid relay info structure for IPv6 can be handled
+TEST_F(ParseConfigTest, bogusRelayInfo6) {
+
// Invalid config (wrong family type of the ip-address field
std::string config_str_bogus1 =
" {"
@@ -2349,23 +2355,25 @@ TEST_F(ParseConfigTest, validRelayInfo6) {
" }";
ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2);
+ // Missing mandatory ip-address field.
+ std::string config_str_bogus3 =
+ " {"
+ " }";
+ ElementPtr json_bogus3 = Element::fromJSON(config_str_bogus3);
+
// We need to set the default ip-address to something.
Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("::")));
- boost::shared_ptr<RelayInfoParser> parser;
- // Subnet4 parser will pass :: to the RelayInfoParser
- EXPECT_NO_THROW(parser.reset(new RelayInfoParser("ignored", result,
- Option::V6)));
- EXPECT_NO_THROW(parser->build(json));
- EXPECT_NO_THROW(parser->commit());
+ RelayInfoParser parser(Option::V6);
- EXPECT_EQ("2001:db8::1", result->addr_.toText());
+ // Negative scenario (wrong family type)
+ EXPECT_THROW(parser.parse(result, json_bogus1), DhcpConfigError);
- // Let's check negative scenario (wrong family type)
- EXPECT_THROW(parser->build(json_bogus1), DhcpConfigError);
+ // Looks like IPv6 address, but has too many colons
+ EXPECT_THROW(parser.parse(result, json_bogus2), DhcpConfigError);
- // Unparseable text that looks like IPv6 address, but has too many colons
- EXPECT_THROW(parser->build(json_bogus2), DhcpConfigError);
+ // Mandatory ip-address is missing. What a pity.
+ EXPECT_THROW(parser.parse(result, json_bogus3), DhcpConfigError);
}
// There's no test for ControlSocketParser, as it is tested in the DHCPv4 code
diff --git a/src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc
index 2b6c524940..be7e96bf9b 100644
--- a/src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,7 +9,9 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/cfg_duid.h>
#include <dhcpsrv/parsers/duid_config_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <dhcpsrv/testutils/config_result_check.h>
+#include <testutils/test_to_element.h>
#include <util/encode/hex.h>
#include <gtest/gtest.h>
#include <limits>
@@ -26,6 +28,13 @@ namespace {
class DUIDConfigParserTest : public ::testing::Test {
public:
+ /// @brief constructor
+ ///
+ /// Initializes cfg_duid_ to a new empty object
+ DUIDConfigParserTest()
+ :cfg_duid_(new CfgDUID()){
+ }
+
/// @brief Creates simple configuration with DUID type only.
///
/// @param duid_type DUID type in the textual format.
@@ -82,9 +91,12 @@ public:
/// @param vec Input vector.
/// @return String of hexadecimal digits converted from vector.
std::string toString(const std::vector<uint8_t>& vec) const;
+
+ /// Config DUID pointer
+ CfgDUIDPtr cfg_duid_;
};
-std::string
+std::string
DUIDConfigParserTest::createConfigWithType(const std::string& duid_type) const {
std::ostringstream s;
s << "{ \"type\": \"" << duid_type << "\" }";
@@ -103,10 +115,9 @@ void
DUIDConfigParserTest::build(const std::string& config) const {
ElementPtr config_element = Element::fromJSON(config);
DUIDConfigParser parser;
- parser.build(config_element);
+ parser.parse(cfg_duid_, config_element);
}
-
void
DUIDConfigParserTest::testTypeOnly(const DUID::DUIDType& duid_type,
const std::string& duid_type_text) const {
@@ -115,12 +126,12 @@ DUIDConfigParserTest::testTypeOnly(const DUID::DUIDType& duid_type,
// Make sure that the type is correct and that other parameters are set
// to their defaults.
- CfgDUIDPtr cfg_duid = CfgMgr::instance().getStagingCfg()->getCfgDUID();
- EXPECT_EQ(duid_type, cfg_duid->getType());
- EXPECT_TRUE(cfg_duid->getIdentifier().empty());
- EXPECT_EQ(0, cfg_duid->getHType());
- EXPECT_EQ(0, cfg_duid->getTime());
- EXPECT_EQ(0, cfg_duid->getEnterpriseId());
+ ASSERT_TRUE(cfg_duid_);
+ EXPECT_EQ(duid_type, cfg_duid_->getType());
+ EXPECT_TRUE(cfg_duid_->getIdentifier().empty());
+ EXPECT_EQ(0, cfg_duid_->getHType());
+ EXPECT_EQ(0, cfg_duid_->getTime());
+ EXPECT_EQ(0, cfg_duid_->getEnterpriseId());
}
void
@@ -172,22 +183,27 @@ TEST_F(DUIDConfigParserTest, noType) {
// This test verifies that all parameters can be set.
TEST_F(DUIDConfigParserTest, allParameters) {
// Set all parameters.
- ASSERT_NO_THROW(build("{ \"type\": \"EN\","
- " \"identifier\": \"ABCDEF\","
- " \"time\": 100,"
- " \"htype\": 8,"
- " \"enterprise-id\": 2024,"
- " \"persist\": false"
- "}"));
+ std::string config = "{"
+ " \"type\": \"EN\","
+ " \"identifier\": \"ABCDEF\","
+ " \"time\": 100,"
+ " \"htype\": 8,"
+ " \"enterprise-id\": 2024,"
+ " \"persist\": false"
+ "}";
+ ASSERT_NO_THROW(build(config));
// Verify that parameters have been set correctly.
- CfgDUIDPtr cfg_duid = CfgMgr::instance().getStagingCfg()->getCfgDUID();
- EXPECT_EQ(DUID::DUID_EN, cfg_duid->getType());
- EXPECT_EQ("ABCDEF", toString(cfg_duid->getIdentifier()));
- EXPECT_EQ(8, cfg_duid->getHType());
- EXPECT_EQ(100, cfg_duid->getTime());
- EXPECT_EQ(2024, cfg_duid->getEnterpriseId());
- EXPECT_FALSE(cfg_duid->persist());
+ ASSERT_TRUE(cfg_duid_);
+ EXPECT_EQ(DUID::DUID_EN, cfg_duid_->getType());
+ EXPECT_EQ("ABCDEF", toString(cfg_duid_->getIdentifier()));
+ EXPECT_EQ(8, cfg_duid_->getHType());
+ EXPECT_EQ(100, cfg_duid_->getTime());
+ EXPECT_EQ(2024, cfg_duid_->getEnterpriseId());
+ EXPECT_FALSE(cfg_duid_->persist());
+
+ // Check the config can be got back.
+ isc::test::runToElementTest<CfgDUID>(config, *cfg_duid_);
}
// Test out of range values for time.
diff --git a/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc
index da2bee8e79..fd76507232 100644
--- a/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/cfg_expiration.h>
#include <dhcpsrv/parsers/expiration_config_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <gtest/gtest.h>
#include <sstream>
#include <stdint.h>
@@ -105,7 +106,7 @@ ExpirationConfigParserTest::renderConfig() const {
// Parse the configuration. This may emit exceptions.
ExpirationConfigParser parser;
- parser.build(config_element);
+ parser.parse(config_element);
// No exception so return configuration.
return (CfgMgr::instance().getStagingCfg()->getCfgExpiration());
@@ -220,15 +221,6 @@ TEST_F(ExpirationConfigParserTest, otherParameters) {
EXPECT_EQ(20, cfg->getUnwarnedReclaimCycles());
}
-// This test verifies that the exception is thrown if unsupported
-// parameter is specified.
-TEST_F(ExpirationConfigParserTest, invalidParameter) {
- addParam("reclaim-timer-wait-time", 20);
- addParam("invalid-parameter", 20);
-
- EXPECT_THROW(renderConfig(), DhcpConfigError);
-}
-
// This test verifies that negative parameter values are not allowed.
TEST_F(ExpirationConfigParserTest, outOfRangeValues) {
testOutOfRange("reclaim-timer-wait-time",
@@ -254,7 +246,7 @@ TEST_F(ExpirationConfigParserTest, notNumberValue) {
// Parse the configuration. It should throw exception.
ExpirationConfigParser parser;
- EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+ EXPECT_THROW(parser.parse(config_element), DhcpConfigError);
}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
index 43a87fb860..75e46c14b4 100644
--- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
+++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -382,7 +382,7 @@ GenericHostDataSourceTest::compareOptions(const ConstCfgOptionPtr& cfg1,
// Options must be represented by the same C++ class derived from
// the Option class.
EXPECT_TRUE(typeid(*option1) == typeid(*option2))
- << "Comapared DHCP options, having option code "
+ << "Compared DHCP options, having option code "
<< desc1.option_->getType() << " and belonging to the "
<< space << " option space, are represented "
"by different C++ classes: "
@@ -513,7 +513,7 @@ GenericHostDataSourceTest::testReadOnlyDatabase(const char* valid_db_type) {
ASSERT_TRUE(hdsptr_);
// The database is initially opened in "read-write" mode. We can
- // insert some data to the databse.
+ // insert some data to the database.
HostPtr host = initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
ASSERT_TRUE(host);
ASSERT_NO_THROW(hdsptr_->add(host));
@@ -823,7 +823,7 @@ GenericHostDataSourceTest::testMultipleSubnets(int subnets,
EXPECT_EQ(1000 + i++, (*it)->getIPv4SubnetID());
}
- // Finally, check that the hosts can be retrived by HW address or DUID
+ // Finally, check that the hosts can be retrieved by HW address or DUID
ConstHostCollection all_by_id =
hdsptr_->getAll(id, &host->getIdentifier()[0],
host->getIdentifier().size());
@@ -945,7 +945,7 @@ GenericHostDataSourceTest::testSubnetId6(int subnets, Host::IdentifierType id) {
EXPECT_EQ(i + 1000, from_hds->getIPv6SubnetID());
}
- // Check that the hosts can all be retrived by HW address or DUID
+ // Check that the hosts can all be retrieved by HW address or DUID
ConstHostCollection all_by_id = hdsptr_->getAll(id, &host->getIdentifier()[0],
host->getIdentifier().size());
ASSERT_EQ(subnets, all_by_id.size());
@@ -1188,7 +1188,7 @@ void GenericHostDataSourceTest::testOptionsReservations4(const bool formatted) {
ASSERT_NO_THROW(addTestOptions(host, formatted, DHCP4_ONLY));
// Insert host and the options into respective tables.
ASSERT_NO_THROW(hdsptr_->add(host));
- // Subnet id will be used in quries to the database.
+ // Subnet id will be used in queries to the database.
SubnetID subnet_id = host->getIPv4SubnetID();
// getAll4(address)
@@ -1261,7 +1261,7 @@ GenericHostDataSourceTest::testMultipleClientClasses4() {
// Add the host.
ASSERT_NO_THROW(hdsptr_->add(host));
- // Subnet id will be used in quries to the database.
+ // Subnet id will be used in queries to the database.
SubnetID subnet_id = host->getIPv4SubnetID();
// Fetch the host via:
@@ -1323,7 +1323,7 @@ GenericHostDataSourceTest::testMultipleClientClasses6() {
// Add the host.
ASSERT_NO_THROW(hdsptr_->add(host));
- // Subnet id will be used in quries to the database.
+ // Subnet id will be used in queries to the database.
SubnetID subnet_id = host->getIPv6SubnetID();
// Fetch the host via:
@@ -1389,7 +1389,7 @@ GenericHostDataSourceTest::testMultipleClientClassesBoth() {
// Add the host.
ASSERT_NO_THROW(hdsptr_->add(host));
- // Subnet id will be used in quries to the database.
+ // Subnet id will be used in queries to the database.
SubnetID subnet_id = host->getIPv6SubnetID();
// Fetch the host from the source.
@@ -1418,7 +1418,7 @@ GenericHostDataSourceTest::testMessageFields4() {
// Add the host.
ASSERT_NO_THROW(hdsptr_->add(host));
- // Subnet id will be used in quries to the database.
+ // Subnet id will be used in queries to the database.
SubnetID subnet_id = host->getIPv4SubnetID();
// Fetch the host via:
@@ -1463,6 +1463,198 @@ GenericHostDataSourceTest::testMessageFields4() {
ASSERT_NO_FATAL_FAILURE(compareHosts(host, from_hds));
}
+void GenericHostDataSourceTest::testDeleteByAddr4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v4 host...
+ HostPtr host1 = initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get4(subnet1, IOAddress("192.0.2.1"));
+
+ // Now try to delete it: del(subnet-id, addr4)
+ EXPECT_TRUE(hdsptr_->del(subnet1, IOAddress("192.0.2.1")));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get4(subnet1, IOAddress("192.0.2.1"));
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+}
+
+void GenericHostDataSourceTest::testDeleteById4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v4 host...
+ HostPtr host1 = initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del4(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+}
+
+// Test checks when a IPv4 host with options is deleted that the options are
+// deleted as well.
+void GenericHostDataSourceTest::testDeleteById4Options() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v4 host...
+ HostPtr host1 = initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+ ASSERT_NO_THROW(addTestOptions(host1, true, DHCP4_ONLY));
+ // Insert host and the options into respective tables.
+
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // There must be some options
+ EXPECT_NE(0, countDBOptions4());
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del4(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // Check the options are indeed gone.
+ EXPECT_EQ(0, countDBOptions4());
+}
+
+void GenericHostDataSourceTest::testDeleteById6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v6 host...
+ HostPtr host1 = initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del6(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+}
+
+void GenericHostDataSourceTest::testDeleteById6Options() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v6 host...
+ HostPtr host1 = initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+ ASSERT_NO_THROW(addTestOptions(host1, true, DHCP6_ONLY));
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // Check that the options are stored...
+ EXPECT_NE(0, countDBOptions6());
+
+ // ... and so are v6 reservations.
+ EXPECT_NE(0, countDBReservations6());
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del6(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // Check the options are indeed gone.
+ EXPECT_EQ(0, countDBOptions6());
+
+ // Check the options are indeed gone.
+ EXPECT_EQ(0, countDBReservations6());
+}
+
}; // namespace test
}; // namespace dhcp
}; // namespace isc
diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
index f84638bb83..c5bef73346 100644
--- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
+++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -88,7 +88,7 @@ public:
/// @brief Compares hardware addresses of the two hosts.
///
- /// This method compares two hwardware address and uses gtest
+ /// This method compares two hardware address and uses gtest
/// macros to signal unexpected (mismatch if expect_match is true;
/// match if expect_match is false) values.
///
@@ -148,7 +148,7 @@ public:
void compareOptions(const ConstCfgOptionPtr& cfg1,
const ConstCfgOptionPtr& cfg2) const;
- /// @brief Creates an opton descriptor holding an empty option.
+ /// @brief Creates an option descriptor holding an empty option.
///
/// @param universe V4 or V6.
/// @param option_type Option type.
@@ -292,6 +292,39 @@ public:
return (desc);
}
+ /// @brief Returns number of entries in the v4 options table.
+ ///
+ /// This utility method is expected to be implemented by specific backends.
+ /// The code here is just a boilerplate for backends that do not store
+ /// host options in a table.
+ ///
+ /// @param number of existing entries in options table
+ virtual int countDBOptions4() {
+ return (-1);
+ }
+
+ /// @brief Returns number of entries in the v6 options table.
+ ///
+ /// This utility method is expected to be implemented by specific backends.
+ /// The code here is just a boilerplate for backends that do not store
+ /// host options in a table.
+ ///
+ /// @param number of existing entries in options table
+ virtual int countDBOptions6() {
+ return (-1);
+ }
+
+ /// @brief Returns number of entries in the v6 reservations table.
+ ///
+ /// This utility method is expected to be implemented by specific backends.
+ /// The code here is just a boilerplate for backends that do not store
+ /// v6 reservations in a table.
+ ///
+ /// @param number of existing entries in v6_reservations table
+ virtual int countDBReservations6() {
+ return (-1);
+ }
+
/// @brief Creates an instance of the vendor option.
///
/// @param universe V4 or V6.
@@ -318,7 +351,7 @@ public:
/// - DHCPv6 boot file url option,
/// - DHCPv6 information refresh time option,
/// - DHCPv6 vendor option with vendor id 2495,
- /// - DHCPv6 option 1024, with a sigle IPv6 address,
+ /// - DHCPv6 option 1024, with a single IPv6 address,
/// - DHCPv6 empty option 1, within isc2 option space,
/// - DHCPv6 option 2, within isc2 option space with 3 IPv6 addresses,
///
@@ -393,7 +426,7 @@ public:
void testClientIdNotHWAddr();
/// @brief Test adds specified number of hosts with unique hostnames, then
- /// retrives them and checks that the hostnames are set properly.
+ /// retrieves them and checks that the hostnames are set properly.
///
/// Uses gtest macros to report failures.
///
@@ -522,9 +555,31 @@ public:
/// from a database for a host.
///
/// Uses gtest macros to report failures.
- ///
void testMessageFields4();
+ /// @brief Tests that delete(subnet-id, addr4) call works.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteByAddr4();
+
+ /// @brief Tests that delete(subnet4-id, identifier-type, identifier) works.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteById4();
+
+ /// @brief Tests that delete(subnet4-id, id-type, id) also deletes options.
+ void testDeleteById4Options();
+
+ /// @brief Tests that delete(subnet6-id, identifier-type, identifier) works.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteById6();
+
+ /// @brief Tests that delete(subnet6-id, id-type, id) also deletes options.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteById6Options();
+
/// @brief Returns DUID with identical content as specified HW address
///
/// This method does not have any sense in real life and is only useful
diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
index 25d1b2bd07..3a0c65783a 100644
--- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -1566,7 +1566,7 @@ GenericLeaseMgrTest::testRecreateLease4() {
EXPECT_TRUE(lmptr_->addLease(lease));
lmptr_->commit();
- // Check that the lease has been successfuly added.
+ // Check that the lease has been successfully added.
Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[0]);
ASSERT_TRUE(l_returned);
detailCompareLease(lease, l_returned);
@@ -1606,7 +1606,7 @@ GenericLeaseMgrTest::testRecreateLease6() {
EXPECT_TRUE(lmptr_->addLease(lease));
lmptr_->commit();
- // Check that the lease has been successfuly added.
+ // Check that the lease has been successfully added.
Lease6Ptr l_returned = lmptr_->getLease6(Lease::TYPE_NA, ioaddress6_[0]);
ASSERT_TRUE(l_returned);
detailCompareLease(lease, l_returned);
@@ -2404,7 +2404,7 @@ void
GenericLeaseMgrTest::checkLeaseStats(const StatValMapList& expectedStats) {
// Global accumulators
int64_t declined_addresses = 0;
- int64_t declined_reclaimed_addresses = 0;
+ int64_t reclaimed_declined_addresses = 0;
// Iterate over all stats for each subnet
for (int subnet_idx = 0; subnet_idx < expectedStats.size(); ++subnet_idx) {
@@ -2417,15 +2417,15 @@ GenericLeaseMgrTest::checkLeaseStats(const StatValMapList& expectedStats) {
// Add the value to globals as needed.
if (expectedStat.first == "declined-addresses") {
declined_addresses += expectedStat.second;
- } else if (expectedStat.first == "declined-reclaimed-addresses") {
- declined_reclaimed_addresses += expectedStat.second;
+ } else if (expectedStat.first == "reclaimed-declined-addresses") {
+ reclaimed_declined_addresses += expectedStat.second;
}
}
}
// Verify the globals.
checkStat("declined-addresses", declined_addresses);
- checkStat("declined-reclaimed-addresses", declined_reclaimed_addresses);
+ checkStat("reclaimed-declined-addresses", reclaimed_declined_addresses);
}
void
@@ -2501,7 +2501,8 @@ GenericLeaseMgrTest::testRecountLeaseStats4() {
expectedStats[i]["total-addresses"] = 256;
expectedStats[i]["assigned-addresses"] = 0;
expectedStats[i]["declined-addresses"] = 0;
- expectedStats[i]["declined-reclaimed-addresses"] = 0;
+ expectedStats[i]["reclaimed-declined-addresses"] = 0;
+ expectedStats[i]["reclaimed-leases"] = 0;
}
// Make sure stats are as expected.
@@ -2535,7 +2536,7 @@ GenericLeaseMgrTest::testRecountLeaseStats4() {
// Now let's add leases to subnet 2.
subnet_id = 2;
- // Insert one delined lease.
+ // Insert one declined lease.
makeLease4("192.0.2.2", subnet_id, Lease::STATE_DECLINED);
// Update the expected stats.
@@ -2604,8 +2605,9 @@ GenericLeaseMgrTest::testRecountLeaseStats6() {
for (int i = 0; i < num_subnets; ++i) {
expectedStats[i]["assigned-nas"] = 0;
expectedStats[i]["declined-addresses"] = 0;
- expectedStats[i]["declined-reclaimed-addresses"] = 0;
+ expectedStats[i]["reclaimed-declined-addresses"] = 0;
expectedStats[i]["assigned-pds"] = 0;
+ expectedStats[i]["reclaimed-leases"] = 0;
}
// Make sure stats are as expected.
@@ -2682,6 +2684,69 @@ GenericLeaseMgrTest::testRecountLeaseStats6() {
ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
}
+void
+GenericLeaseMgrTest::testWipeLeases6() {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease6Ptr> leases = createLeases6();
+ leases[0]->subnet_id_ = 1;
+ leases[1]->subnet_id_ = 1;
+ leases[2]->subnet_id_ = 1;
+ leases[3]->subnet_id_ = 22;
+ leases[4]->subnet_id_ = 333;
+ leases[5]->subnet_id_ = 333;
+ leases[6]->subnet_id_ = 333;
+ leases[7]->subnet_id_ = 333;
+
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Let's try something simple. There shouldn't be any leases in
+ // subnet 2. The keep deleting the leases, perhaps in a different
+ // order they were added.
+ EXPECT_EQ(0, lmptr_->wipeLeases6(2));
+ EXPECT_EQ(4, lmptr_->wipeLeases6(333));
+ EXPECT_EQ(3, lmptr_->wipeLeases6(1));
+ EXPECT_EQ(1, lmptr_->wipeLeases6(22));
+
+ // All the leases should be gone now. Check that that repeated
+ // attempt to delete them will not result in any additional removals.
+ EXPECT_EQ(0, lmptr_->wipeLeases6(1));
+ EXPECT_EQ(0, lmptr_->wipeLeases6(22));
+ EXPECT_EQ(0, lmptr_->wipeLeases6(333));
+}
+
+void
+GenericLeaseMgrTest::testWipeLeases4() {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ leases[0]->subnet_id_ = 1;
+ leases[1]->subnet_id_ = 1;
+ leases[2]->subnet_id_ = 1;
+ leases[3]->subnet_id_ = 22;
+ leases[4]->subnet_id_ = 333;
+ leases[5]->subnet_id_ = 333;
+ leases[6]->subnet_id_ = 333;
+ leases[7]->subnet_id_ = 333;
+
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Let's try something simple. There shouldn't be any leases in
+ // subnet 2. The keep deleting the leases, perhaps in a different
+ // order they were added.
+ EXPECT_EQ(0, lmptr_->wipeLeases4(2));
+ EXPECT_EQ(4, lmptr_->wipeLeases4(333));
+ EXPECT_EQ(3, lmptr_->wipeLeases4(1));
+ EXPECT_EQ(1, lmptr_->wipeLeases4(22));
+
+ // All the leases should be gone now. Check that that repeated
+ // attempt to delete them will not result in any additional removals.
+ EXPECT_EQ(0, lmptr_->wipeLeases4(1));
+ EXPECT_EQ(0, lmptr_->wipeLeases4(22));
+ EXPECT_EQ(0, lmptr_->wipeLeases4(333));
+}
}; // namespace test
}; // namespace dhcp
diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
index a5dd8ce365..c2a77d31b5 100644
--- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
+++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -307,7 +307,7 @@ public:
/// @brief Checks that the expired DHCPv4 leases can be retrieved.
///
/// This test checks the following:
- /// - all expired and not reclaimed leases are retured
+ /// - all expired and not reclaimed leases are returned
/// - number of leases returned can be limited
/// - leases are returned in the order from the most expired to the
/// least expired
@@ -317,7 +317,7 @@ public:
/// @brief Checks that the expired IPv6 leases can be retrieved.
///
/// This test checks the following:
- /// - all expired and not reclaimed leases are retured
+ /// - all expired and not reclaimed leases are returned
/// - number of leases returned can be limited
/// - leases are returned in the order from the most expired to the
/// least expired
@@ -374,6 +374,19 @@ public:
/// after altering the lease states in various ways.
void testRecountLeaseStats6();
+
+ /// @brief Check if wipeLeases4 works properly.
+ ///
+ /// This test creates a bunch of leases in several subnets and then
+ /// attempts to delete them, one subnet at a time.
+ void testWipeLeases4();
+
+ /// @brief Check if wipeLeases6 works properly.
+ ///
+ /// This test creates a bunch of leases in several subnets and then
+ /// attempts to delete them, one subnet at a time.
+ void testWipeLeases6();
+
/// @brief String forms of IPv4 addresses
std::vector<std::string> straddress4_;
diff --git a/src/lib/dhcpsrv/tests/host_mgr_unittest.cc b/src/lib/dhcpsrv/tests/host_mgr_unittest.cc
index 8e922d355d..1f7cc680f5 100644
--- a/src/lib/dhcpsrv/tests/host_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/host_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -388,6 +388,18 @@ TEST_F(HostMgrTest, get6ByPrefix) {
testGet6ByPrefix(*getCfgHosts(), *getCfgHosts());
}
+// This test verifies that without a host data source an exception is thrown.
+TEST_F(HostMgrTest, addNoDataSource) {
+ // Remove all configuration.
+ CfgMgr::instance().clear();
+ // Recreate HostMgr instance.
+ HostMgr::create();
+
+ HostPtr host(new Host(hwaddrs_[0]->toText(false), "hw-address",
+ SubnetID(1), SubnetID(0), IOAddress("192.0.2.5")));
+ EXPECT_THROW(HostMgr::instance().add(host), NoHostDataSourceManager);
+}
+
// The following tests require MySQL enabled.
#if defined HAVE_MYSQL
@@ -432,13 +444,13 @@ MySQLHostMgrTest::TearDown() {
}
// This test verifies that reservations for a particular client can
-// be retrieved from the confguration file and a database simultaneously.
+// be retrieved from the configuration file and a database simultaneously.
TEST_F(MySQLHostMgrTest, getAll) {
testGetAll(*getCfgHosts(), HostMgr::instance());
}
// This test verifies that IPv4 reservations for a particular client can
-// be retrieved from the configuration file and a database simulatneously.
+// be retrieved from the configuration file and a database simultaneously.
TEST_F(MySQLHostMgrTest, getAll4) {
testGetAll4(*getCfgHosts(), HostMgr::instance());
}
@@ -509,13 +521,13 @@ PostgreSQLHostMgrTest::TearDown() {
}
// This test verifies that reservations for a particular client can
-// be retrieved from the confguration file and a database simultaneously.
+// be retrieved from the configuration file and a database simultaneously.
TEST_F(PostgreSQLHostMgrTest, getAll) {
testGetAll(*getCfgHosts(), HostMgr::instance());
}
// This test verifies that IPv4 reservations for a particular client can
-// be retrieved from the configuration file and a database simulatneously.
+// be retrieved from the configuration file and a database simultaneously.
TEST_F(PostgreSQLHostMgrTest, getAll4) {
testGetAll4(*getCfgHosts(), HostMgr::instance());
}
diff --git a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
index 5c72c91149..6ddfa7a93f 100644
--- a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -14,10 +14,14 @@
#include <dhcp/option4_addrlst.h>
#include <dhcp/option6_addrlst.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_hosts_util.h>
#include <dhcpsrv/host.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <dhcpsrv/parsers/host_reservation_parser.h>
#include <dhcpsrv/testutils/config_result_check.h>
+#include <testutils/test_to_element.h>
#include <boost/pointer_cast.hpp>
+#include <boost/algorithm/string.hpp>
#include <gtest/gtest.h>
#include <iterator>
#include <sstream>
@@ -27,6 +31,7 @@
using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::dhcp;
+using namespace isc::test;
namespace {
@@ -115,29 +120,26 @@ protected:
ElementPtr config_element = Element::fromJSON(config);
- ParserType parser(SubnetID(10));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ ParserType parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
- // Retrieve a host.
- HostCollection hosts;
- CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
- ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
- ASSERT_EQ(1, hosts.size());
+ ASSERT_TRUE(host);
// There should be no options assigned to a host.
- EXPECT_TRUE(hosts[0]->getCfgOption4()->empty());
- EXPECT_TRUE(hosts[0]->getCfgOption6()->empty());
+ EXPECT_TRUE(host->getCfgOption4()->empty());
+ EXPECT_TRUE(host->getCfgOption6()->empty());
}
- /// @brief This test verfies that the parser can parse a DHCPv4
+ /// @brief This test verifies that the parser can parse a DHCPv4
/// reservation configuration including a specific identifier.
///
/// @param identifier_name Identifier name.
/// @param identifier_type Identifier type.
void testIdentifier4(const std::string& identifier_name,
const std::string& identifier_value,
- const Host::IdentifierType& expected_identifier_type,
- const std::vector<uint8_t>& expected_identifier) const {
+ const Host::IdentifierType& /*expected_identifier_type*/,
+ const std::vector<uint8_t>& /*expected_identifier*/) const {
std::ostringstream config;
config << "{ \"" << identifier_name << "\": \"" << identifier_value
<< "\","
@@ -146,24 +148,18 @@ protected:
ElementPtr config_element = Element::fromJSON(config.str());
- HostReservationParser4 parser(SubnetID(10));
- ASSERT_NO_THROW(parser.build(config_element));
-
- CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
- HostCollection hosts;
- ASSERT_NO_THROW(hosts = cfg_hosts->getAll(expected_identifier_type,
- &expected_identifier[0],
- expected_identifier.size()));
-
- ASSERT_EQ(1, hosts.size());
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
- EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
- EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
- EXPECT_EQ("192.0.2.112", hosts[0]->getIPv4Reservation().toText());
- EXPECT_TRUE(hosts[0]->getHostname().empty());
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(0, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.112", host->getIPv4Reservation().toText());
+ EXPECT_TRUE(host->getHostname().empty());
}
- /// @brief This test verfies that the parser returns an error when
+ /// @brief This test verifies that the parser returns an error when
/// configuration is invalid.
///
/// @param config JSON configuration to be tested.
@@ -171,8 +167,12 @@ protected:
template<typename ParserType>
void testInvalidConfig(const std::string& config) const {
ElementPtr config_element = Element::fromJSON(config);
- ParserType parser(SubnetID(10));
- EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+ HostPtr host;
+ ParserType parser;
+ EXPECT_THROW({
+ host = parser.parse(SubnetID(10), config_element);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ }, isc::Exception);
}
/// @brief HW Address object used by tests.
@@ -215,7 +215,78 @@ HostReservationParserTest::TearDown() {
CfgMgr::instance().clear();
}
-// This test verfies that the parser can parse the reservation entry for
+/// @brief class of subnet_id reservations
+class CfgHostsSubnet : public CfgToElement {
+public:
+ /// @brief constructor
+ CfgHostsSubnet(ConstCfgHostsPtr hosts, SubnetID id)
+ : hosts_(hosts), id_(id) { }
+
+ /// @brief unparse method
+ ElementPtr toElement() const;
+
+private:
+ /// @brief the host reservation configuration
+ ConstCfgHostsPtr hosts_;
+
+ /// @brief the subnet ID
+ SubnetID id_;
+};
+
+ElementPtr
+CfgHostsSubnet::toElement() const {
+ CfgHostsList list;
+ try {
+ list.internalize(hosts_->toElement());
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "CfgHostsSubnet::toElement: " << ex.what();
+ }
+ ElementPtr result = boost::const_pointer_cast<Element>(list.get(id_));
+
+ // Strip
+ for (size_t i = 0; i < result->size(); ++i) {
+ ElementPtr resv = result->getNonConst(i);
+ ConstElementPtr ip_address = resv->get("ip-address");
+ if (ip_address && (ip_address->stringValue() == "0.0.0.0")) {
+ resv->remove("ip-address");
+ }
+ ConstElementPtr ip_addresses = resv->get("ip-addresses");
+ if (ip_addresses && ip_addresses->empty()) {
+ resv->remove("ip-addresses");
+ }
+ ConstElementPtr prefixes = resv->get("prefixes");
+ if (prefixes && prefixes->empty()) {
+ resv->remove("prefixes");
+ }
+ ConstElementPtr hostname = resv->get("hostname");
+ if (hostname && hostname->stringValue().empty()) {
+ resv->remove("hostname");
+ }
+ ConstElementPtr next_server = resv->get("next-server");
+ if (next_server && (next_server->stringValue() == "0.0.0.0")) {
+ resv->remove("next-server");
+ }
+ ConstElementPtr server_hostname = resv->get("server-hostname");
+ if (server_hostname && server_hostname->stringValue().empty()) {
+ resv->remove("server-hostname");
+ }
+ ConstElementPtr boot_file_name = resv->get("boot-file-name");
+ if (boot_file_name && boot_file_name->stringValue().empty()) {
+ resv->remove("boot-file-name");
+ }
+ ConstElementPtr client_classess = resv->get("client-classes");
+ if (client_classess && client_classess->empty()) {
+ resv->remove("client-classes");
+ }
+ ConstElementPtr option_data = resv->get("option-data");
+ if (option_data && option_data->empty()) {
+ resv->remove("option-data");
+ }
+ }
+ return (result);
+}
+
+// This test verifies that the parser can parse the reservation entry for
// which hw-address is a host identifier.
TEST_F(HostReservationParserTest, dhcp4HWaddr) {
testIdentifier4("hw-address", "1:2:3:4:5:6", Host::IDENT_HWADDR,
@@ -280,15 +351,18 @@ TEST_F(HostReservationParserTest, dhcp4ClientIdHexWithPrefix) {
// This test verifies that the parser can parse the reservation entry
// when IPv4 address is specified, but hostname is not.
TEST_F(HostReservationParserTest, dhcp4NoHostname) {
- std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0a\","
"\"ip-address\": \"192.0.2.10\" }";
ElementPtr config_element = Element::fromJSON(config);
- HostReservationParser4 parser(SubnetID(10));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
@@ -298,6 +372,19 @@ TEST_F(HostReservationParserTest, dhcp4NoHostname) {
EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
EXPECT_EQ("192.0.2.10", hosts[0]->getIPv4Reservation().toText());
EXPECT_TRUE(hosts[0]->getHostname().empty());
+
+ // lower duid value
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that it is possible to specify DHCPv4 client classes
@@ -308,10 +395,14 @@ TEST_F(HostReservationParserTest, dhcp4ClientClasses) {
ElementPtr config_element = Element::fromJSON(config);
- HostReservationParser4 parser(SubnetID(10));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_));
@@ -321,6 +412,18 @@ TEST_F(HostReservationParserTest, dhcp4ClientClasses) {
ASSERT_EQ(2, classes.size());
EXPECT_EQ(1, classes.count("foo"));
EXPECT_EQ(1, classes.count("bar"));
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that the parser can parse reservation entry
@@ -334,10 +437,14 @@ TEST_F(HostReservationParserTest, dhcp4MessageFields) {
ElementPtr config_element = Element::fromJSON(config);
- HostReservationParser4 parser(SubnetID(10));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
&hwaddr_->hwaddr_[0],
@@ -349,6 +456,24 @@ TEST_F(HostReservationParserTest, dhcp4MessageFields) {
EXPECT_EQ("192.0.2.11", hosts[0]->getNextServer().toText());
EXPECT_EQ("some-name.example.org", hosts[0]->getServerHostname());
EXPECT_EQ("/tmp/some-file.efi", hosts[0]->getBootFileName());
+
+ // canonize hw-address
+ config_element->set("hw-address",
+ Element::create(std::string("01:02:03:04:05:06")));
+ ElementPtr expected = Element::createList();
+ expected->add(config_element);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that the invalid value of the next server is rejected.
@@ -421,10 +546,14 @@ TEST_F(HostReservationParserTest, noIPAddress) {
ElementPtr config_element = Element::fromJSON(config);
- HostReservationParser4 parser(SubnetID(10));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
@@ -434,6 +563,20 @@ TEST_F(HostReservationParserTest, noIPAddress) {
EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
EXPECT_EQ("0.0.0.0", hosts[0]->getIPv4Reservation().toText());
EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+ // lower duid value
+ boost::algorithm::to_lower(config);
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that the configuration parser for host reservations
@@ -505,7 +648,7 @@ TEST_F(HostReservationParserTest, invalidParameterName) {
testInvalidConfig<HostReservationParser4>(config);
}
-// This test verfies that the parser can parse the IPv6 reservation entry for
+// This test verifies that the parser can parse the IPv6 reservation entry for
// which hw-address is a host identifier.
TEST_F(HostReservationParserTest, dhcp6HWaddr) {
std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
@@ -516,10 +659,14 @@ TEST_F(HostReservationParserTest, dhcp6HWaddr) {
ElementPtr config_element = Element::fromJSON(config);
- HostReservationParser6 parser(SubnetID(10));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ HostReservationParser6 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_, DuidPtr()));
@@ -551,9 +698,27 @@ TEST_F(HostReservationParserTest, dhcp6HWaddr) {
64),
prefixes));
+ // canonize prefixes
+ config_element->set("prefixes",
+ Element::fromJSON("[ \"2001:db8:2000:101::/64\", "
+ "\"2001:db8:2000:102::/64\" ]"));
+ ElementPtr expected = Element::createList();
+ expected->add(config_element);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
-// This test verfies that the parser can parse the IPv6 reservation entry for
+// This test verifies that the parser can parse the IPv6 reservation entry for
// which DUID is a host identifier.
TEST_F(HostReservationParserTest, dhcp6DUID) {
std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
@@ -563,10 +728,14 @@ TEST_F(HostReservationParserTest, dhcp6DUID) {
ElementPtr config_element = Element::fromJSON(config);
- HostReservationParser6 parser(SubnetID(12));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ HostReservationParser6 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(12), config_element));
+ ASSERT_TRUE(host);
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
@@ -589,6 +758,23 @@ TEST_F(HostReservationParserTest, dhcp6DUID) {
IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
+
+ // remove prefixes and lower duid value
+ config_element->remove("prefixes");
+ config = prettyPrint(config_element);
+ boost::algorithm::to_lower(config);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(12));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(12));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that host reservation parser for DHCPv6 rejects
@@ -613,7 +799,7 @@ TEST_F(HostReservationParserTest, dhcp6ClientId) {
testInvalidConfig<HostReservationParser6>(config);
}
-// This test verfies that the parser can parse the IPv6 reservation entry
+// This test verifies that the parser can parse the IPv6 reservation entry
// which lacks hostname parameter.
TEST_F(HostReservationParserTest, dhcp6NoHostname) {
std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
@@ -622,10 +808,14 @@ TEST_F(HostReservationParserTest, dhcp6NoHostname) {
ElementPtr config_element = Element::fromJSON(config);
- HostReservationParser6 parser(SubnetID(12));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ HostReservationParser6 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(12), config_element));
+ ASSERT_TRUE(host);
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
@@ -648,6 +838,23 @@ TEST_F(HostReservationParserTest, dhcp6NoHostname) {
IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
+
+ // remove prefixes and lower duid value
+ config_element->remove("prefixes");
+ config = prettyPrint(config_element);
+ boost::algorithm::to_lower(config);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(12));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(12));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that it is possible to specify DHCPv4 client classes
@@ -658,10 +865,14 @@ TEST_F(HostReservationParserTest, dhcp6ClientClasses) {
ElementPtr config_element = Element::fromJSON(config);
- HostReservationParser6 parser(SubnetID(10));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ HostReservationParser6 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
&duid_->getDuid()[0],
@@ -672,6 +883,20 @@ TEST_F(HostReservationParserTest, dhcp6ClientClasses) {
ASSERT_EQ(2, classes.size());
EXPECT_EQ(1, classes.count("foo"));
EXPECT_EQ(1, classes.count("bar"));
+
+ // lower duid value
+ boost::algorithm::to_lower(config);
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that the configuration parser throws an exception
@@ -759,7 +984,8 @@ TEST_F(HostReservationParserTest, options4) {
"\"code\": 7,"
"\"csv-format\": true,"
"\"space\": \"dhcp4\","
- "\"data\": \"172.16.15.23\""
+ "\"data\": \"172.16.15.23\","
+ "\"always-send\": false"
"},"
"{"
"\"name\": \"default-ip-ttl\","
@@ -769,10 +995,14 @@ TEST_F(HostReservationParserTest, options4) {
ElementPtr config_element = Element::fromJSON(config);
- HostReservationParser4 parser(SubnetID(10));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_));
ASSERT_EQ(1, hosts.size());
@@ -799,6 +1029,34 @@ TEST_F(HostReservationParserTest, options4) {
OptionUint8>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL));
ASSERT_TRUE(opt_ttl);
EXPECT_EQ(64, opt_ttl->getValue());
+
+ // Canonize the config
+ ElementPtr option = config_element->get("option-data")->getNonConst(0);
+ option->set("code", Element::create(DHO_NAME_SERVERS));
+ option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
+ option->set("csv-format", Element::create(true));
+ option->set("always-send", Element::create(false));
+ option = config_element->get("option-data")->getNonConst(1);
+ option = config_element->get("option-data")->getNonConst(2);
+ option->set("code", Element::create(DHO_DEFAULT_IP_TTL));
+ option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
+ option->set("csv-format", Element::create(true));
+ option->set("always-send", Element::create(false));
+ ElementPtr expected = Element::createList();
+ expected->add(config_element);
+
+ // Try to unparse it.
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that it is possible to specify DHCPv6 options for
@@ -816,7 +1074,8 @@ TEST_F(HostReservationParserTest, options6) {
"\"code\": 27,"
"\"csv-format\": true,"
"\"space\": \"dhcp6\","
- "\"data\": \"2001:db8:1::1204\""
+ "\"data\": \"2001:db8:1::1204\","
+ "\"always-send\": true"
"},"
"{"
"\"name\": \"preference\","
@@ -826,11 +1085,15 @@ TEST_F(HostReservationParserTest, options6) {
ElementPtr config_element = Element::fromJSON(config);
- HostReservationParser6 parser(SubnetID(10));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostPtr host;
+ HostReservationParser6 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
// One host should have been added to the configuration.
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
ASSERT_EQ(1, hosts.size());
@@ -857,6 +1120,34 @@ TEST_F(HostReservationParserTest, options6) {
OptionUint8>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_PREFERENCE));
ASSERT_TRUE(opt_prf);
EXPECT_EQ(11, opt_prf->getValue());
+
+ // Canonize the config
+ ElementPtr option = config_element->get("option-data")->getNonConst(0);
+ option->set("code", Element::create(D6O_NAME_SERVERS));
+ option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ option->set("csv-format", Element::create(true));
+ option->set("always-send", Element::create(false));
+ option = config_element->get("option-data")->getNonConst(1);
+ option = config_element->get("option-data")->getNonConst(2);
+ option->set("code", Element::create(D6O_PREFERENCE));
+ option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ option->set("csv-format", Element::create(true));
+ option->set("always-send", Element::create(false));
+ config = prettyPrint(config_element);
+ boost::algorithm::to_lower(config);
+
+ // Try to unparse it.
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that it is possible to specify an empty list of
@@ -981,7 +1272,7 @@ public:
void testInvalidConfig(const std::string& config) const {
ElementPtr config_element = Element::fromJSON(config);
ParserType parser;
- EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+ EXPECT_THROW(parser.parse(config_element), DhcpConfigError);
}
};
@@ -995,7 +1286,7 @@ TEST_F(HostReservationIdsParserTest, dhcp4Identifiers) {
ElementPtr config_element = Element::fromJSON(config);
HostReservationIdsParser4 parser;
- ASSERT_NO_THROW(parser.build(config_element));
+ ASSERT_NO_THROW(parser.parse(config_element));
ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
getCfgHostOperations4();
@@ -1007,6 +1298,8 @@ TEST_F(HostReservationIdsParserTest, dhcp4Identifiers) {
EXPECT_EQ(*id++, Host::IDENT_DUID);
EXPECT_EQ(*id++, Host::IDENT_HWADDR);
EXPECT_EQ(*id++, Host::IDENT_CLIENT_ID);
+
+ runToElementTest<CfgHostOperations>(config, *cfg);
}
// Test that list of supported DHCPv6 identifiers list is correctly
@@ -1017,7 +1310,7 @@ TEST_F(HostReservationIdsParserTest, dhcp6Identifiers) {
ElementPtr config_element = Element::fromJSON(config);
HostReservationIdsParser6 parser;
- ASSERT_NO_THROW(parser.build(config_element));
+ ASSERT_NO_THROW(parser.parse(config_element));
ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
getCfgHostOperations6();
@@ -1027,6 +1320,8 @@ TEST_F(HostReservationIdsParserTest, dhcp6Identifiers) {
CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
EXPECT_EQ(*id++, Host::IDENT_DUID);
EXPECT_EQ(*id++, Host::IDENT_HWADDR);
+
+ runToElementTest<CfgHostOperations>(config, *cfg);
}
// Test that invalid DHCPv4 identifier causes error.
@@ -1051,18 +1346,19 @@ TEST_F(HostReservationIdsParserTest, dhcp4AutoIdentifiers) {
ElementPtr config_element = Element::fromJSON(config);
HostReservationIdsParser4 parser;
- ASSERT_NO_THROW(parser.build(config_element));
+ ASSERT_NO_THROW(parser.parse(config_element));
ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
getCfgHostOperations4();
const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
- ASSERT_EQ(4, ids.size());
+ ASSERT_EQ(5, ids.size());
CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
EXPECT_EQ(*id++, Host::IDENT_HWADDR);
EXPECT_EQ(*id++, Host::IDENT_DUID);
EXPECT_EQ(*id++, Host::IDENT_CIRCUIT_ID);
EXPECT_EQ(*id++, Host::IDENT_CLIENT_ID);
+ EXPECT_EQ(*id++, Host::IDENT_FLEX);
}
// This test verifies that use of "auto" together with an explicit
@@ -1094,16 +1390,17 @@ TEST_F(HostReservationIdsParserTest, dhcp6AutoIdentifiers) {
ElementPtr config_element = Element::fromJSON(config);
HostReservationIdsParser6 parser;
- ASSERT_NO_THROW(parser.build(config_element));
+ ASSERT_NO_THROW(parser.parse(config_element));
ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
getCfgHostOperations6();
const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
- ASSERT_EQ(2, ids.size());
+ ASSERT_EQ(3, ids.size());
CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
EXPECT_EQ(*id++, Host::IDENT_HWADDR);
EXPECT_EQ(*id++, Host::IDENT_DUID);
+ EXPECT_EQ(*id++, Host::IDENT_FLEX);
}
// This test verifies that use of "auto" together with an explicit
diff --git a/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc b/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc
index 0103793a71..9d9ea64c2b 100644
--- a/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,10 +11,14 @@
#include <dhcp/hwaddr.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_hosts_util.h>
#include <dhcpsrv/host.h>
#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <dhcpsrv/parsers/host_reservation_parser.h>
#include <dhcpsrv/parsers/host_reservations_list_parser.h>
+#include <testutils/test_to_element.h>
+#include <boost/algorithm/string.hpp>
#include <gtest/gtest.h>
#include <sstream>
#include <string>
@@ -22,6 +26,7 @@
using namespace isc::data;
using namespace isc::dhcp;
+using namespace isc::test;
namespace {
@@ -66,9 +71,82 @@ HostReservationsListParserTest::TearDown() {
CfgMgr::instance().clear();
}
+/// @brief class of subnet_id reservations
+class CfgHostsSubnet : public CfgToElement {
+public:
+ /// @brief constructor
+ CfgHostsSubnet(ConstCfgHostsPtr hosts, SubnetID id)
+ : hosts_(hosts), id_(id) { }
+
+ /// @brief unparse method
+ ElementPtr toElement() const;
+
+private:
+ /// @brief the host reservation configuration
+ ConstCfgHostsPtr hosts_;
+
+ /// @brief the subnet ID
+ SubnetID id_;
+};
+
+ElementPtr
+CfgHostsSubnet::toElement() const {
+ CfgHostsList list;
+ try {
+ list.internalize(hosts_->toElement());
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "CfgHostsSubnet::toElement: " << ex.what();
+ }
+ ElementPtr result = boost::const_pointer_cast<Element>(list.get(id_));
+
+ // Strip
+ for (size_t i = 0; i < result->size(); ++i) {
+ ElementPtr resv = result->getNonConst(i);
+ ConstElementPtr ip_address = resv->get("ip-address");
+ if (ip_address && (ip_address->stringValue() == "0.0.0.0")) {
+ resv->remove("ip-address");
+ }
+ ConstElementPtr ip_addresses = resv->get("ip-addresses");
+ if (ip_addresses && ip_addresses->empty()) {
+ resv->remove("ip-addresses");
+ }
+ ConstElementPtr prefixes = resv->get("prefixes");
+ if (prefixes && prefixes->empty()) {
+ resv->remove("prefixes");
+ }
+ ConstElementPtr hostname = resv->get("hostname");
+ if (hostname && hostname->stringValue().empty()) {
+ resv->remove("hostname");
+ }
+ ConstElementPtr next_server = resv->get("next-server");
+ if (next_server && (next_server->stringValue() == "0.0.0.0")) {
+ resv->remove("next-server");
+ }
+ ConstElementPtr server_hostname = resv->get("server-hostname");
+ if (server_hostname && server_hostname->stringValue().empty()) {
+ resv->remove("server-hostname");
+ }
+ ConstElementPtr boot_file_name = resv->get("boot-file-name");
+ if (boot_file_name && boot_file_name->stringValue().empty()) {
+ resv->remove("boot-file-name");
+ }
+ ConstElementPtr client_classess = resv->get("client-classes");
+ if (client_classess && client_classess->empty()) {
+ resv->remove("client-classes");
+ }
+ ConstElementPtr option_data = resv->get("option-data");
+ if (option_data && option_data->empty()) {
+ resv->remove("option-data");
+ }
+ }
+ return (result);
+}
+
// This test verifies that the parser for the list of the host reservations
// parses IPv4 reservations correctly.
TEST_F(HostReservationsListParserTest, ipv4Reservations) {
+ CfgMgr::instance().setFamily(AF_INET);
+ // hexadecimal in lower case for toElement()
std::string config =
"[ "
" { "
@@ -85,11 +163,15 @@ TEST_F(HostReservationsListParserTest, ipv4Reservations) {
ElementPtr config_element = Element::fromJSON(config);
- HostReservationsListParser<HostReservationParser4> parser(SubnetID(1));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser4> parser;
+ ASSERT_NO_THROW(parser.parse(SubnetID(1), config_element, hosts));
+
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
- HostCollection hosts;
// Get the first reservation for the host identified by the HW address.
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_));
@@ -108,6 +190,19 @@ TEST_F(HostReservationsListParserTest, ipv4Reservations) {
EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
EXPECT_EQ("192.0.2.110", hosts[0]->getIPv4Reservation().toText());
EXPECT_EQ("bar.example.com", hosts[0]->getHostname());
+
+ // Get back the config from cfg_hosts
+ boost::algorithm::to_lower(config);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>(config, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(0));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that an attempt to add two reservations with the
@@ -117,6 +212,7 @@ TEST_F(HostReservationsListParserTest, duplicatedIdentifierValue4) {
identifiers.push_back("hw-address");
identifiers.push_back("duid");
identifiers.push_back("circuit-id");
+ identifiers.push_back("flex-id");
for (unsigned int i = 0; i < identifiers.size(); ++i) {
SCOPED_TRACE("Using identifier " + identifiers[i]);
@@ -138,14 +234,27 @@ TEST_F(HostReservationsListParserTest, duplicatedIdentifierValue4) {
ElementPtr config_element = Element::fromJSON(config.str());
- HostReservationsListParser<HostReservationParser4> parser(SubnetID(1));
- EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser4> parser;
+ EXPECT_THROW({
+ parser.parse(SubnetID(1), config_element, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+ }, DuplicateHost);
+ // The code threw exception, because the second insertion failed.
+ // Nevertheless, the first host insertion succeeded, so the next
+ // time we try to insert them, we will get ReservedAddress exception,
+ // rather than DuplicateHost. Therefore we need to remove the
+ // first host that's still there.
+ CfgMgr::instance().clear();
}
}
// This test verifies that the parser for the list of the host reservations
// parses IPv6 reservations correctly.
TEST_F(HostReservationsListParserTest, ipv6Reservations) {
+ // hexadecimal in lower case for toElement()
std::string config =
"[ "
" { \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
@@ -155,7 +264,6 @@ TEST_F(HostReservationsListParserTest, ipv6Reservations) {
" }, "
" { \"hw-address\": \"01:02:03:04:05:06\","
" \"ip-addresses\": [ \"2001:db8:1::123\" ],"
- " \"prefixes\": [ ],"
" \"hostname\": \"bar.example.com\" "
" } "
"]";
@@ -163,11 +271,15 @@ TEST_F(HostReservationsListParserTest, ipv6Reservations) {
ElementPtr config_element = Element::fromJSON(config);
// Parse configuration.
- HostReservationsListParser<HostReservationParser6> parser(SubnetID(2));
- ASSERT_NO_THROW(parser.build(config_element));
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser6> parser;
+ ASSERT_NO_THROW(parser.parse(SubnetID(2), config_element, hosts));
+
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
- HostCollection hosts;
// Get the reservation for the host identified by the HW address.
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_));
@@ -201,6 +313,23 @@ TEST_F(HostReservationsListParserTest, ipv6Reservations) {
EXPECT_EQ(IPv6Resrv::TYPE_PD, prefixes.first->second.getType());
EXPECT_EQ("2001:db8:1:2::", prefixes.first->second.getPrefix().toText());
EXPECT_EQ(80, prefixes.first->second.getPrefixLen());
+
+ // Get back the config from cfg_hosts
+ ElementPtr resv = config_element->getNonConst(0);
+ resv->remove("ip-addresses");
+ config = prettyPrint(config_element);
+ boost::algorithm::to_lower(config);
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(2));
+ runToElementTest<CfgHostsSubnet>(config, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(2));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
// This test verifies that an attempt to add two reservations with the
@@ -209,6 +338,7 @@ TEST_F(HostReservationsListParserTest, duplicatedIdentifierValue6) {
std::vector<std::string> identifiers;
identifiers.push_back("hw-address");
identifiers.push_back("duid");
+ identifiers.push_back("flex-id");
for (unsigned int i = 0; i < identifiers.size(); ++i) {
SCOPED_TRACE("Using identifier " + identifiers[i]);
@@ -230,11 +360,15 @@ TEST_F(HostReservationsListParserTest, duplicatedIdentifierValue6) {
ElementPtr config_element = Element::fromJSON(config.str());
- HostReservationsListParser<HostReservationParser6> parser(SubnetID(1));
- EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser6> parser;
+ EXPECT_THROW({
+ parser.parse(SubnetID(1), config_element, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+ }, DuplicateHost);
}
}
-
-
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/host_unittest.cc b/src/lib/dhcpsrv/tests/host_unittest.cc
index a2477879e1..fad37178ec 100644
--- a/src/lib/dhcpsrv/tests/host_unittest.cc
+++ b/src/lib/dhcpsrv/tests/host_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -182,11 +182,12 @@ TEST_F(HostTest, getIdentifier) {
EXPECT_EQ(Host::IDENT_DUID, Host::getIdentifierType("duid"));
EXPECT_EQ(Host::IDENT_CIRCUIT_ID, Host::getIdentifierType("circuit-id"));
EXPECT_EQ(Host::IDENT_CLIENT_ID, Host::getIdentifierType("client-id"));
+ EXPECT_EQ(Host::IDENT_FLEX, Host::getIdentifierType("flex-id"));
- EXPECT_THROW(Host::getIdentifierType("unuspported"), isc::BadValue);
+ EXPECT_THROW(Host::getIdentifierType("unsupported"), isc::BadValue);
}
-// This test verfies that it is possible to create a Host object
+// This test verifies that it is possible to create a Host object
// using hardware address in the textual format.
TEST_F(HostTest, createFromHWAddrString) {
boost::scoped_ptr<Host> host;
@@ -407,7 +408,7 @@ TEST_F(HostTest, createFromIdentifierString) {
const std::string identifier_name = Host::getIdentifierName(type);
// Construct unique identifier for a host. This is a string
- // consisting of a word "idenetifier", hyphen and the name of
+ // consisting of a word "identifier", hyphen and the name of
// the identifier, e.g. "identifier-hw-address".
std::ostringstream identifier_without_quotes;
identifier_without_quotes << "identifier-" << identifier_name;
@@ -680,7 +681,7 @@ TEST_F(HostTest, setValues) {
EXPECT_THROW(host->setIPv4Reservation(IOAddress("2001:db8:1::1")),
isc::BadValue);
// Zero address can't be set, the removeIPv4Reservation should be
- // used intead.
+ // used instead.
EXPECT_THROW(host->setIPv4Reservation(IOAddress::IPV4_ZERO_ADDRESS()),
isc::BadValue);
// Broadcast address can't be set.
diff --git a/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc
index 67eed945ca..8cc6c4779e 100644
--- a/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,11 +10,13 @@
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/parsers/ifaces_config_parser.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
+using namespace isc::test;
namespace {
@@ -51,13 +53,19 @@ TEST_F(IfacesConfigParserTest, interfaces) {
IfaceMgrTestConfig test_config(true);
// Configuration with one interface.
- std::string config = "{ ""\"interfaces\": [ \"eth0\" ] }";
+ std::string config =
+ "{ \"interfaces\": [ \"eth0\" ], \"re-detect\": false }";
ElementPtr config_element = Element::fromJSON(config);
// Parse the configuration.
- IfacesConfigParser4 parser;
- ASSERT_NO_THROW(parser.build(config_element));
+ IfacesConfigParser parser(AF_INET);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
// Open sockets according to the parsed configuration.
SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
@@ -74,10 +82,14 @@ TEST_F(IfacesConfigParserTest, interfaces) {
// Try similar configuration but this time add a wildcard interface
// to see if sockets will open on all interfaces.
- config = "{ \"interfaces\": [ \"eth0\", \"*\" ] }";
+ config = "{ \"interfaces\": [ \"eth0\", \"*\" ], \"re-detect\": false }";
config_element = Element::fromJSON(config);
- ASSERT_NO_THROW(parser.build(config_element));
+ cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ runToElementTest<CfgIface>(config, *cfg_iface);
cfg = CfgMgr::instance().getStagingCfg();
ASSERT_NO_THROW(cfg->getCfgIface()->openSockets(AF_INET, 10000));
@@ -95,13 +107,15 @@ TEST_F(IfacesConfigParserTest, socketTypeRaw) {
// Configuration with a raw socket selected.
std::string config = "{ ""\"interfaces\": [ ],"
- " \"dhcp-socket-type\": \"raw\" }";
+ " \"dhcp-socket-type\": \"raw\","
+ " \"re-detect\": false }";
ElementPtr config_element = Element::fromJSON(config);
// Parse the configuration.
- IfacesConfigParser4 parser;
- ASSERT_NO_THROW(parser.build(config_element));
+ IfacesConfigParser parser(AF_INET);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
// Compare the resulting configuration with a reference
// configuration using the raw socket.
@@ -119,38 +133,48 @@ TEST_F(IfacesConfigParserTest, socketTypeDatagram) {
CfgIface cfg_ref;
// Configuration with a datagram socket selected.
- std::string config = "{ ""\"interfaces\": [ ],"
- " \"dhcp-socket-type\": \"udp\" }";
+ std::string config = "{ \"interfaces\": [ ],"
+ " \"dhcp-socket-type\": \"udp\","
+ " \"re-detect\": false }";
ElementPtr config_element = Element::fromJSON(config);
// Parse the configuration.
- IfacesConfigParser4 parser;
- ASSERT_NO_THROW(parser.build(config_element));
+ IfacesConfigParser parser(AF_INET);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
// Compare the resulting configuration with a reference
// configuration using the raw socket.
SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
ASSERT_TRUE(cfg);
cfg_ref.useSocketType(AF_INET, CfgIface::SOCKET_UDP);
+ ASSERT_TRUE(cfg->getCfgIface());
EXPECT_TRUE(*cfg->getCfgIface() == cfg_ref);
}
// Test that the configuration rejects the invalid socket type.
TEST_F(IfacesConfigParserTest, socketTypeInvalid) {
// For DHCPv4 we only accept the raw socket or datagram socket.
- IfacesConfigParser4 parser4;
+ IfacesConfigParser parser4(AF_INET);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
std::string config = "{ \"interfaces\": [ ],"
- "\"dhcp-socket-type\": \"default\" }";
+ "\"dhcp-socket-type\": \"default\","
+ " \"re-detect\": false }";
ElementPtr config_element = Element::fromJSON(config);
- ASSERT_THROW(parser4.build(config_element), DhcpConfigError);
+ ASSERT_THROW(parser4.parse(cfg_iface, config_element), DhcpConfigError);
// For DHCPv6 we don't accept any socket type.
- IfacesConfigParser6 parser6;
+ IfacesConfigParser parser6(AF_INET6);
config = "{ \"interfaces\": [ ],"
- " \"dhcp-socket-type\": \"udp\" }";
+ " \"dhcp-socket-type\": \"udp\","
+ " \"re-detect\": false }";
config_element = Element::fromJSON(config);
- ASSERT_THROW(parser6.build(config_element), DhcpConfigError);
+ ASSERT_THROW(parser6.parse(cfg_iface, config_element), DhcpConfigError);
}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_file_io.h b/src/lib/dhcpsrv/tests/lease_file_io.h
index abd92dab72..2b03a566d0 100644
--- a/src/lib/dhcpsrv/tests/lease_file_io.h
+++ b/src/lib/dhcpsrv/tests/lease_file_io.h
@@ -23,7 +23,7 @@ class LeaseFileIO {
public:
/// @brief Constructor
///
- /// @param filename Abolsute path to the file.
+ /// @param filename Absolute path to the file.
/// @param recreate A boolean flag indicating if the new file should
/// be created, even if one exists.
LeaseFileIO(const std::string& filename, const bool recreate = true);
diff --git a/src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc b/src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc
index 7b030eb47d..3efd15fc54 100644
--- a/src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -99,7 +99,7 @@ public:
LeaseFileLoader::write<LeaseObjectType, LeaseFileType, StorageType>
(lease_file, storage);
- // Compare to see if we got what we exepcted.
+ // Compare to see if we got what we expected.
EXPECT_EQ(compare, io_.readFile());
}
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index 3a894da998..05dccca6bc 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-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -223,6 +223,18 @@ public:
" is not implemented");
}
+ /// @brief Pretends to wipe all IPv4 leases from a subnet
+ /// @param subnet_id (ignored, but one day may specify the subnet)
+ virtual size_t wipeLeases4(const SubnetID&) {
+ isc_throw(NotImplemented, "ConreteLeaseMgr::wipeLeases4 not implemented");
+ }
+
+ /// @brief Pretends to wipe all IPv4 leases from a subnet
+ /// @param subnet_id (ignored, but one day may specify the subnet)
+ virtual size_t wipeLeases6(const SubnetID&) {
+ isc_throw(NotImplemented, "ConreteLeaseMgr::wipeLeases4 not implemented");
+ }
+
/// @brief Returns backend type.
///
/// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
diff --git a/src/lib/dhcpsrv/tests/lease_unittest.cc b/src/lib/dhcpsrv/tests/lease_unittest.cc
index e3c3ac1b7c..827ccd98db 100644
--- a/src/lib/dhcpsrv/tests/lease_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,8 @@
#include <dhcp/duid.h>
#include <dhcpsrv/lease.h>
#include <util/pointer_util.h>
+#include <testutils/test_to_element.h>
+#include <cc/data.h>
#include <gtest/gtest.h>
#include <vector>
#include <sstream>
@@ -16,6 +18,8 @@
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::test;
namespace {
@@ -98,7 +102,7 @@ TEST_F(Lease4Test, constructor) {
}
}
-// This test verfies that copy constructor copies Lease4 fields correctly.
+// This test verifies that copy constructor copies Lease4 fields correctly.
TEST_F(Lease4Test, copyConstructor) {
// Get current time for the use in Lease4.
@@ -134,7 +138,7 @@ TEST_F(Lease4Test, copyConstructor) {
EXPECT_TRUE(lease == copied_lease2);
}
-// This test verfies that the assignment operator copies all Lease4 fields
+// This test verifies that the assignment operator copies all Lease4 fields
// correctly.
TEST_F(Lease4Test, operatorAssign) {
@@ -423,6 +427,46 @@ TEST_F(Lease4Test, toText) {
EXPECT_EQ(expected.str(), lease.toText());
}
+// Verify that Lease4 structure can be converted to JSON properly.
+TEST_F(Lease4Test, toElement) {
+
+ const time_t current_time = 12345678;
+ Lease4 lease(IOAddress("192.0.2.3"), hwaddr_, clientid_, 3600, 123,
+ 456, current_time, 789, true, true, "urania.example.org");
+
+ std::string expected = "{"
+ "\"client-id\": \"17:34:e2:ff:09:92:54\","
+ "\"cltt\": 12345678,"
+ "\"fqdn-fwd\": true,"
+ "\"fqdn-rev\": true,"
+ "\"hostname\": \"urania.example.org\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"ip-address\": \"192.0.2.3\","
+ "\"state\": 0,"
+ "\"subnet-id\": 789,"
+ "\"valid-lft\": 3600 "
+ "}";
+
+ runToElementTest<Lease4>(expected, lease);
+
+ // Now let's try with a lease without client-id.
+ lease.client_id_.reset();
+
+ expected = "{"
+ "\"cltt\": 12345678,"
+ "\"fqdn-fwd\": true,"
+ "\"fqdn-rev\": true,"
+ "\"hostname\": \"urania.example.org\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"ip-address\": \"192.0.2.3\","
+ "\"state\": 0,"
+ "\"subnet-id\": 789,"
+ "\"valid-lft\": 3600 "
+ "}";
+
+ runToElementTest<Lease4>(expected, lease);
+}
+
// Verify that decline() method properly clears up specific fields.
TEST_F(Lease4Test, decline) {
@@ -803,7 +847,7 @@ TEST(Lease6Test, hasIdenticalFqdn) {
false, false)));
}
-// Verify that toText() method reports Lease4 structure properly.
+// Verify that toText() method reports Lease6 structure properly.
TEST(Lease6Test, toText) {
HWAddrPtr hwaddr(new HWAddr(HWADDR, sizeof(HWADDR), HTYPE_ETHER));
@@ -848,6 +892,127 @@ TEST(Lease6Test, toText) {
EXPECT_EQ(expected.str(), lease.toText());
}
+// Verify that Lease6 structure can be converted to JSON properly.
+// This tests an address lease conversion.
+TEST(Lease6Test, toElementAddress) {
+
+ HWAddrPtr hwaddr(new HWAddr(HWADDR, sizeof(HWADDR), HTYPE_ETHER));
+
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+ Lease6 lease(Lease::TYPE_NA, IOAddress("2001:db8::1"), duid, 123456,
+ 400, 800, 100, 200, 5678, hwaddr, 128);
+ lease.cltt_ = 12345678;
+ lease.state_ = Lease::STATE_DECLINED;
+ lease.hostname_ = "urania.example.org";
+
+ std::string expected = "{"
+ "\"cltt\": 12345678,"
+ "\"duid\": \"00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f\","
+ "\"fqdn-fwd\": false,"
+ "\"fqdn-rev\": false,"
+ "\"hostname\": \"urania.example.org\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"iaid\": 123456,"
+ "\"ip-address\": \"2001:db8::1\","
+ "\"preferred-lft\": 400,"
+ "\"state\": 1,"
+ "\"subnet-id\": 5678,"
+ "\"type\": \"IA_NA\","
+ "\"valid-lft\": 800"
+ "}";
+
+ runToElementTest<Lease6>(expected, lease);
+
+ // Now let's try with a lease without hardware address.
+ lease.hwaddr_.reset();
+
+ expected = "{"
+ "\"cltt\": 12345678,"
+ "\"duid\": \"00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f\","
+ "\"fqdn-fwd\": false,"
+ "\"fqdn-rev\": false,"
+ "\"hostname\": \"urania.example.org\","
+ "\"iaid\": 123456,"
+ "\"ip-address\": \"2001:db8::1\","
+ "\"preferred-lft\": 400,"
+ "\"state\": 1,"
+ "\"subnet-id\": 5678,"
+ "\"type\": \"IA_NA\","
+ "\"valid-lft\": 800"
+ "}";
+
+ runToElementTest<Lease6>(expected, lease);
+}
+
+// Verify that Lease6 structure can be converted to JSON properly.
+// This tests an address lease conversion.
+TEST(Lease6Test, toElementPrefix) {
+
+ HWAddrPtr hwaddr(new HWAddr(HWADDR, sizeof(HWADDR), HTYPE_ETHER));
+
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+ Lease6 lease(Lease::TYPE_PD, IOAddress("2001:db8::"), duid, 123456,
+ 400, 800, 100, 200, 5678, hwaddr, 56);
+ lease.cltt_ = 12345678;
+ lease.state_ = Lease::STATE_DEFAULT;
+ lease.hostname_ = "urania.example.org";
+
+ ElementPtr l = lease.toElement();
+
+ ASSERT_TRUE(l);
+
+ ASSERT_TRUE(l->contains("ip-address"));
+ EXPECT_EQ("2001:db8::", l->get("ip-address")->stringValue());
+
+ ASSERT_TRUE(l->contains("type"));
+ EXPECT_EQ("IA_PD", l->get("type")->stringValue());
+
+ // This is a prefix lease, it must have a prefix length.
+ ASSERT_TRUE(l->contains("prefix-len"));
+ EXPECT_EQ(56, l->get("prefix-len")->intValue());
+
+ ASSERT_TRUE(l->contains("iaid"));
+ EXPECT_EQ(123456, l->get("iaid")->intValue());
+
+ ASSERT_TRUE(l->contains("preferred-lft"));
+ EXPECT_EQ(400, l->get("preferred-lft")->intValue());
+
+ ASSERT_TRUE(l->contains("valid-lft"));
+ EXPECT_EQ(800, l->get("valid-lft")->intValue());
+
+ ASSERT_TRUE(l->contains("duid"));
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f",
+ l->get("duid")->stringValue());
+
+ ASSERT_TRUE(l->contains("hw-address"));
+ EXPECT_EQ(hwaddr->toText(false), l->get("hw-address")->stringValue());
+
+ ASSERT_TRUE(l->contains("subnet-id"));
+ EXPECT_EQ(5678, l->get("subnet-id")->intValue());
+
+ ASSERT_TRUE(l->contains("state"));
+ EXPECT_EQ(static_cast<int>(Lease::STATE_DEFAULT),
+ l->get("state")->intValue());
+
+ ASSERT_TRUE(l->contains("fqdn-fwd"));
+ EXPECT_FALSE(l->get("fqdn-fwd")->boolValue());
+
+ ASSERT_TRUE(l->contains("fqdn-rev"));
+ EXPECT_FALSE(l->get("fqdn-rev")->boolValue());
+
+ ASSERT_TRUE(l->contains("hostname"));
+ EXPECT_EQ("urania.example.org", l->get("hostname")->stringValue());
+
+ // Now let's try with a lease without hardware address.
+ lease.hwaddr_.reset();
+ l = lease.toElement();
+ EXPECT_FALSE(l->contains("hw-address"));
+}
+
// Verify that the lease states are correctly returned in the textual format.
TEST(Lease6Test, stateToText) {
EXPECT_EQ("default", Lease6::statesToText(Lease::STATE_DEFAULT));
diff --git a/src/lib/dhcpsrv/tests/logging_info_unittest.cc b/src/lib/dhcpsrv/tests/logging_info_unittest.cc
index 0e495774b2..f7474c94ee 100644
--- a/src/lib/dhcpsrv/tests/logging_info_unittest.cc
+++ b/src/lib/dhcpsrv/tests/logging_info_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,14 +7,16 @@
#include <config.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/logging_info.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
using namespace isc::dhcp;
+using namespace isc::test;
namespace {
// Checks if two destinations can be compared for equality.
-TEST(LoggingDestintaion, equals) {
+TEST(LoggingDestination, equals) {
LoggingDestination dest1;
LoggingDestination dest2;
@@ -69,6 +71,17 @@ TEST_F(LoggingInfoTest, defaults) {
ASSERT_EQ(1, info_non_verbose.destinations_.size());
EXPECT_EQ("stdout", info_non_verbose.destinations_[0].output_);
+ std::string header = "{\n"
+ "\"name\": \"kea\",\n"
+ "\"output_options\": [ {\n"
+ " \"output\": \"stdout\",\n \"maxsize\": 10240000,\n"
+ " \"maxver\": 1,\n \"flush\": true } ],\n"
+ "\"severity\": \"";
+ std::string dbglvl = "\",\n\"debuglevel\": ";
+ std::string trailer = "\n}\n";
+ std::string expected = header + "INFO" + dbglvl + "0" + trailer;
+ runToElementTest<LoggingInfo>(expected, info_non_verbose);
+
CfgMgr::instance().setVerbose(true);
LoggingInfo info_verbose;
EXPECT_EQ("kea", info_verbose.name_);
@@ -77,6 +90,12 @@ TEST_F(LoggingInfoTest, defaults) {
ASSERT_EQ(1, info_verbose.destinations_.size());
EXPECT_EQ("stdout", info_verbose.destinations_[0].output_);
+
+ EXPECT_EQ(10240000, info_verbose.destinations_[0].maxsize_);
+ EXPECT_EQ(1, info_verbose.destinations_[0].maxver_);
+
+ expected = header + "DEBUG" + dbglvl + "99" + trailer;
+ runToElementTest<LoggingInfo>(expected, info_verbose);
}
// Checks if (in)equality operators work for LoggingInfo.
@@ -120,7 +139,7 @@ TEST_F(LoggingInfoTest, equalityOperators) {
EXPECT_TRUE(info1 == info2);
EXPECT_FALSE(info1 != info2);
- // Create two different desinations.
+ // Create two different destinations.
LoggingDestination dest1;
LoggingDestination dest2;
dest1.output_ = "foo";
diff --git a/src/lib/dhcpsrv/tests/logging_unittest.cc b/src/lib/dhcpsrv/tests/logging_unittest.cc
index 8450fac27d..51d96d820d 100644
--- a/src/lib/dhcpsrv/tests/logging_unittest.cc
+++ b/src/lib/dhcpsrv/tests/logging_unittest.cc
@@ -1,16 +1,19 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
-#include <exceptions/exceptions.h>
#include <cc/data.h>
#include <config/module_spec.h>
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/logging.h>
-#include <gtest/gtest.h>
+#include <exceptions/exceptions.h>
#include <log/logger_support.h>
+#include <testutils/io_utils.h>
+
+#include <gtest/gtest.h>
using namespace isc;
using namespace isc::dhcp;
@@ -24,7 +27,6 @@ namespace {
/// each test. Strictly speaking this only resets the testing root logger (which
/// has the name "kea") but as the only other logger mentioned here ("wombat")
/// is not used elsewhere, that is sufficient.
-
class LoggingTest : public ::testing::Test {
public:
/// @brief Constructor
@@ -34,17 +36,53 @@ class LoggingTest : public ::testing::Test {
///
/// Reset root logger back to defaults.
~LoggingTest() {
- isc::log::setDefaultLoggingOutput();
+ isc::log::initLogger();
+ wipeFiles();
}
+
+ /// @brief Generates a log file name suffixed with a rotation number
+ /// @param rotation number to the append to the end of the file
+ std::string logName(int rotation) {
+ std::ostringstream os;
+ os << TEST_LOG_NAME << "." << rotation;
+ return (os.str());
+ }
+
+ /// @brief Removes the base log file name and 1 rotation
+ void wipeFiles() {
+ static_cast<void>(remove(TEST_LOG_NAME));
+ for (int i = 1; i < TEST_MAX_VERS + 1; ++i) {
+ static_cast<void>(remove(logName(i).c_str()));
+ }
+
+ // Remove the lock file
+ std::ostringstream os;
+ os << TEST_LOG_NAME << ".lock";
+ static_cast<void>(remove(os.str().c_str()));
+ }
+
+ /// @brief Name of the log file
+ static const char* TEST_LOG_NAME;
+
+ /// @brief Maximum log size
+ static const int TEST_MAX_SIZE;
+
+ /// @brief Maximum rotated log versions
+ static const int TEST_MAX_VERS;
+
};
+const char* LoggingTest::TEST_LOG_NAME = "kea.test.log";
+const int LoggingTest::TEST_MAX_SIZE = 204800; // Smallest without disabling rotation
+const int LoggingTest::TEST_MAX_VERS = 2; // More than the default of 1
+
// Tests that the spec file is valid.
TEST_F(LoggingTest, basicSpec) {
std::string specfile = std::string(LOGGING_SPEC_FILE);
ASSERT_NO_THROW(isc::config::moduleSpecFromFile(specfile));
}
-// Checks that contructor is able to process specified storage properly
+// Checks that the constructor is able to process specified storage properly.
TEST_F(LoggingTest, constructor) {
SrvConfigPtr null_ptr;
@@ -244,12 +282,75 @@ TEST_F(LoggingTest, multipleLoggingDestinations) {
EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[1].flush_);
}
+// Verifies that log rotation occurs when configured. We do not
+// worry about contents of the log files, only that rotation occurs.
+// Such details are tested in lib/log. This test verifies that
+// we can correcty configure logging such that rotation occurs as
+// expected.
+TEST_F(LoggingTest, logRotate) {
+ wipeFiles();
+
+ std::ostringstream os;
+ os <<
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \""
+ << TEST_LOG_NAME << "\"," <<
+ " \"flush\": true,"
+ " \"maxsize\":"
+ << TEST_MAX_SIZE << "," <<
+ " \"maxver\":"
+ << TEST_MAX_VERS <<
+ " }"
+ " ],"
+ " \"debuglevel\": 99,"
+ " \"severity\": \"DEBUG\""
+ " }"
+ "]}";
+
+ // Create our server config container.
+ SrvConfigPtr server_cfg(new SrvConfig());
+
+ // LogConfigParser expects a list of loggers, so parse
+ // the JSON text and extract the "loggers" element from it
+ ConstElementPtr config = Element::fromJSON(os.str());
+ config = config->get("loggers");
+
+ // Parse the config and then apply it.
+ LogConfigParser parser(server_cfg);
+ ASSERT_NO_THROW(parser.parseConfiguration(config));
+ ASSERT_NO_THROW(server_cfg->applyLoggingCfg());
+
+ EXPECT_EQ(TEST_MAX_SIZE, server_cfg->getLoggingInfo()[0].destinations_[0].maxsize_);
+ EXPECT_EQ(TEST_MAX_VERS, server_cfg->getLoggingInfo()[0].destinations_[0].maxver_);
+
+ // Make sure we have the initial log file.
+ ASSERT_TRUE(isc::test::fileExists(TEST_LOG_NAME));
+
+ // Now generate a log we know will be large enough to force a rotation.
+ // We borrow a one argument log message for the test.
+ std::string big_arg(TEST_MAX_SIZE, 'x');
+ isc::log::Logger logger("kea");
+
+ for (int i = 1; i < TEST_MAX_VERS + 1; i++) {
+ // Output the big log and make sure we get the expected rotation file.
+ LOG_INFO(logger, DHCPSRV_CFGMGR_ADD_IFACE).arg(big_arg);
+ EXPECT_TRUE(isc::test::fileExists(logName(i).c_str()));
+ }
+
+ // Clean up.
+ wipeFiles();
+}
+
/// @todo Add tests for malformed logging configuration
/// @todo There is no easy way to test applyConfiguration() and defaultLogging().
/// To test them, it would require instrumenting log4cplus to actually fake
/// the logging set up. Alternatively, we could develop set of test suites
-/// that check each logging destination spearately (e.g. configure log file, then
+/// that check each logging destination separately (e.g. configure log file, then
/// check if the file is indeed created or configure stdout destination, then
/// swap console file descriptors and check that messages are really logged.
diff --git a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
index f52f2c1bd3..8471ff7ed8 100644
--- a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,6 +6,7 @@
#include <config.h>
#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcp/iface_mgr.h>
@@ -102,8 +103,11 @@ public:
MemfileLeaseMgrTest() :
io4_(getLeaseFilePath("leasefile4_0.csv")),
io6_(getLeaseFilePath("leasefile6_0.csv")),
+ io_service_(new IOService()),
timer_mgr_(TimerMgr::instance()) {
+ timer_mgr_->setIOService(io_service_);
+
std::ostringstream s;
s << KEA_LFC_BUILD_DIR << "/kea-lfc";
setenv("KEA_LFC_EXECUTABLE", s.str().c_str(), 1);
@@ -130,7 +134,6 @@ public:
/// destroys lease manager backend.
virtual ~MemfileLeaseMgrTest() {
// Stop TimerMgr worker thread if it is running.
- timer_mgr_->stopThread();
// Make sure there are no timers registered.
timer_mgr_->unregisterTimers();
LeaseMgrFactory::destroy();
@@ -143,7 +146,7 @@ public:
/// @brief Remove files being products of Lease File Cleanup.
///
/// @param base_name Path to the lease file name. This file is removed
- /// and all files which names are crated from this name (having specific
+ /// and all files which names are created from this name (having specific
/// suffixes used by Lease File Cleanup mechanism).
void removeFiles(const std::string& base_name) const {
// Generate suffixes and append them to the base name. The
@@ -207,12 +210,13 @@ public:
///
/// @param ms Duration in milliseconds.
void setTestTime(const uint32_t ms) {
- // Measure test time and exit if timeout hit.
- Stopwatch stopwatch;
- while (stopwatch.getTotalMilliseconds() < ms) {
- // Block for one 1 millisecond.
- IfaceMgr::instance().receive6(0, 1000);
- }
+ IntervalTimer timer(*io_service_);
+ timer.setup([this]() {
+ io_service_->stop();
+ }, ms, IntervalTimer::ONE_SHOT);
+
+ io_service_->run();
+ io_service_->get_io_service().reset();
}
/// @brief Waits for the specified process to finish.
@@ -344,6 +348,9 @@ public:
/// @brief Object providing access to v6 lease IO.
LeaseFileIO io6_;
+ /// @brief Pointer to the IO service used by the tests.
+ IOServicePtr io_service_;
+
/// @brief Pointer to the instance of the @c TimerMgr.
TimerMgrPtr timer_mgr_;
};
@@ -375,6 +382,12 @@ TEST_F(MemfileLeaseMgrTest, constructor) {
EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue);
}
+// Checks if there is no lease manager NoLeaseManager is thrown.
+TEST_F(MemfileLeaseMgrTest, noLeaseManager) {
+ LeaseMgrFactory::destroy();
+ EXPECT_THROW(LeaseMgrFactory::instance(), NoLeaseManager);
+}
+
// Checks if the getType() and getName() methods both return "memfile".
TEST_F(MemfileLeaseMgrTest, getTypeAndName) {
startBackend(V4);
@@ -403,7 +416,7 @@ TEST_F(MemfileLeaseMgrTest, getLeaseFilePath) {
EXPECT_TRUE(lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V6).empty());
}
-// Check if the persitLeases correctly checks that leases should not be written
+// Check if the persistLeases correctly checks that leases should not be written
// to disk when disabled through configuration.
TEST_F(MemfileLeaseMgrTest, persistLeases) {
// Initialize IO objects, so as the test csv files get removed after the
@@ -447,15 +460,9 @@ TEST_F(MemfileLeaseMgrTest, lfcTimer) {
boost::scoped_ptr<LFCMemfileLeaseMgr>
lease_mgr(new LFCMemfileLeaseMgr(pmap));
- // Start worker thread to execute LFC periodically.
- ASSERT_NO_THROW(timer_mgr_->startThread());
-
// Run the test for at most 2.9 seconds.
setTestTime(2900);
- // Stop worker thread.
- ASSERT_NO_THROW(timer_mgr_->stopThread());
-
// Within 2.9 we should record two LFC executions.
EXPECT_EQ(2, lease_mgr->getLFCCount());
}
@@ -474,17 +481,9 @@ TEST_F(MemfileLeaseMgrTest, lfcTimerDisabled) {
boost::scoped_ptr<LFCMemfileLeaseMgr>
lease_mgr(new LFCMemfileLeaseMgr(pmap));
- // Start worker thread to execute LFC periodically.
- ASSERT_NO_THROW(timer_mgr_->startThread());
-
// Run the test for at most 1.9 seconds.
setTestTime(1900);
- // Stop worker thread to make sure it is not running when lease
- // manager is destroyed. The lease manager will be unable to
- // unregster timer when the thread is active.
- ASSERT_NO_THROW(timer_mgr_->stopThread());
-
// There should be no LFC execution recorded.
EXPECT_EQ(0, lease_mgr->getLFCCount());
}
@@ -1230,7 +1229,7 @@ TEST_F(MemfileLeaseMgrTest, load4LFCInProgress) {
ASSERT_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)),
DbOpenError);
- // Remove the pid file, and retry. The bakckend should be created.
+ // Remove the pid file, and retry. The backend should be created.
pid_file.deleteFile();
ASSERT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
}
@@ -1480,7 +1479,7 @@ TEST_F(MemfileLeaseMgrTest, load6LFCInProgress) {
ASSERT_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)),
DbOpenError);
- // Remove the pid file, and retry. The bakckend should be created.
+ // Remove the pid file, and retry. The backend should be created.
pid_file.deleteFile();
ASSERT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
}
@@ -1647,7 +1646,7 @@ TEST_F(MemfileLeaseMgrTest, lease4ContainerIndexUpdate) {
// pick a lease.
std::vector<IOAddress> lease_addresses;
- // Generarate random leases. We remember their addresses in
+ // Generate random leases. We remember their addresses in
// lease_addresses.
for (uint32_t i = 0; i < leases_cnt; ++i) {
Lease4Ptr lease = initiateRandomLease4(addr);
@@ -1786,7 +1785,7 @@ TEST_F(MemfileLeaseMgrTest, lease6ContainerIndexUpdate) {
// pick a lease.
std::vector<IOAddress> lease_addresses;
- // Generarate random leases. We remember their addresses in
+ // Generate random leases. We remember their addresses in
// lease_addresses.
for (uint32_t i = 0; i < leases_cnt; ++i) {
Lease6Ptr lease = initiateRandomLease6(addr);
@@ -1895,4 +1894,16 @@ TEST_F(MemfileLeaseMgrTest, recountLeaseStats6) {
testRecountLeaseStats6();
}
+// Tests that leases from specific subnet can be removed.
+TEST_F(MemfileLeaseMgrTest, wipeLeases4) {
+ startBackend(V4);
+ testWipeLeases4();
+}
+
+// Tests that leases from specific subnet can be removed.
+TEST_F(MemfileLeaseMgrTest, wipeLeases6) {
+ startBackend(V6);
+ testWipeLeases6();
+}
+
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
index c6dbffd684..2ca2b0f67a 100644
--- a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -86,6 +86,49 @@ public:
hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
}
+ /// @brief returns number of rows in a table
+ ///
+ /// Note: This method uses its own connection. It will not work if your test
+ /// uses transactions.
+ ///
+ /// @param name of the table
+ /// @return number of rows currently present in the table
+ int countRowsInTable(const std::string& table) {
+ string query = "SELECT * FROM " + table;
+
+ MySqlConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+ MySqlConnection conn(params);
+ conn.openDatabase();
+ int status = mysql_query(conn.mysql_, query.c_str());
+ if (status !=0) {
+ isc_throw(DbOperationError, "Query failed: " << mysql_error(conn.mysql_));
+ }
+
+ MYSQL_RES * res = mysql_store_result(conn.mysql_);
+ int numrows = static_cast<int>(mysql_num_rows(res));
+ mysql_free_result(res);
+
+ return (numrows);
+ }
+
+ /// @brief Returns number of IPv4 options currently stored in DB.
+ virtual int countDBOptions4() {
+ return (countRowsInTable("dhcp4_options"));
+ }
+
+ /// @brief Returns number of IPv4 options currently stored in DB.
+ virtual int countDBOptions6() {
+ return (countRowsInTable("dhcp6_options"));
+ }
+
+ /// @brief Returns number of IPv6 reservations currently stored in DB.
+ virtual int countDBReservations6() {
+ return (countRowsInTable("ipv6_reservations"));
+ }
+
};
/// @brief Check that database can be opened
@@ -264,6 +307,12 @@ TEST_F(MySqlHostDataSourceTest, get4ByCircuitId) {
testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
}
+// Test verifies if a host reservation can be added and later retrieved by
+// client-id.
+TEST_F(MySqlHostDataSourceTest, get4ByClientId) {
+ testGet4ByIdentifier(Host::IDENT_CLIENT_ID);
+}
+
// Test verifies if hardware address and client identifier are not confused.
TEST_F(MySqlHostDataSourceTest, hwaddrNotClientId1) {
testHWAddrNotClientId();
@@ -529,4 +578,31 @@ TEST_F(MySqlHostDataSourceTest, messageFields) {
testMessageFields4();
}
+// Check that delete(subnet-id, addr4) works.
+TEST_F(MySqlHostDataSourceTest, deleteByAddr4) {
+ testDeleteByAddr4();
+}
+
+// Check that delete(subnet4-id, identifier-type, identifier) works.
+TEST_F(MySqlHostDataSourceTest, deleteById4) {
+ testDeleteById4();
+}
+
+// Check that delete(subnet4-id, identifier-type, identifier) works,
+// even when options are present.
+TEST_F(MySqlHostDataSourceTest, deleteById4Options) {
+ testDeleteById4Options();
+}
+
+// Check that delete(subnet6-id, identifier-type, identifier) works.
+TEST_F(MySqlHostDataSourceTest, deleteById6) {
+ testDeleteById6();
+}
+
+// Check that delete(subnet6-id, identifier-type, identifier) works,
+// even when options are present.
+TEST_F(MySqlHostDataSourceTest, deleteById6Options) {
+ testDeleteById6Options();
+}
+
}; // Of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index b5433ab332..bb2b5463f3 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -116,7 +116,7 @@ TEST(MySqlOpenTest, OpenDatabase) {
// Check that lease manager open the database opens correctly with a longer
// timeout. If it fails, print the error message.
try {
- string connection_string = validMySQLConnectionString() + string(" ") +
+ string connection_string = validMySQLConnectionString() + string(" ") +
string(VALID_TIMEOUT);
LeaseMgrFactory::create(connection_string);
EXPECT_NO_THROW((void) LeaseMgrFactory::instance());
@@ -487,4 +487,14 @@ TEST_F(MySqlLeaseMgrTest, recountLeaseStats6) {
testRecountLeaseStats6();
}
+// Tests that leases from specific subnet can be removed.
+TEST_F(MySqlLeaseMgrTest, DISABLED_wipeLeases4) {
+ testWipeLeases4();
+}
+
+// Tests that leases from specific subnet can be removed.
+TEST_F(MySqlLeaseMgrTest, DISABLED_wipeLeases6) {
+ testWipeLeases6();
+}
+
}; // Of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/ncr_generator_unittest.cc b/src/lib/dhcpsrv/tests/ncr_generator_unittest.cc
index b04d488c93..e7a9d32d9a 100644
--- a/src/lib/dhcpsrv/tests/ncr_generator_unittest.cc
+++ b/src/lib/dhcpsrv/tests/ncr_generator_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -110,7 +110,7 @@ public:
///
/// @param type An expected type of the NameChangeRequest (Add or Remove).
/// @param reverse An expected setting of the reverse update flag.
- /// @param forward An expected setting of the forward udpate flag.
+ /// @param forward An expected setting of the forward update flag.
/// @param addr A string representation of the IPv6 address held in the
/// NameChangeRequest.
/// @param dhcid An expected DHCID value.
diff --git a/src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc
index 509bf7950c..6f6ba77dc5 100644
--- a/src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc
+++ b/src/lib/dhcpsrv/tests/pgsql_exchange_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -175,7 +175,7 @@ public:
const std::string& expectedColumnName(int col) {
if (col < 0 || col >= NUM_BASIC_COLS) {
isc_throw(BadValue,
- "definedColunName: invalid column value" << col);
+ "definedColumnName: invalid column value" << col);
}
return (expectedColNames_[col]);
@@ -216,7 +216,7 @@ public:
/// @brief Executes a SQL statement and tests for an expected outcome
///
/// @param r pointer which will contain the result set returned by the
- /// statment's execution.
+ /// statement's execution.
/// @param sql string containing the SQL statement text. Note that
/// PostgreSQL supports executing text which contains more than one SQL
/// statement separated by semicolons.
@@ -236,7 +236,7 @@ public:
/// @brief Executes a SQL statement and tests for an expected outcome
///
/// @param r pointer which will contain the result set returned by the
- /// statment's execution.
+ /// statement's execution.
/// @param statement statement descriptor of the prepared statement
/// to execute.
/// @param bind_array bind array containing the input values to submit
@@ -268,7 +268,7 @@ public:
/// of the defined columns, in the order they are defined.
///
/// @param r pointer which will contain the result set returned by the
- /// statment's execution.
+ /// statement's execution.
/// @param exp_rows expected number of rows fetched. (This can be 0).
/// @lineno line number from where the call was invoked
///
@@ -302,13 +302,13 @@ public:
#define FETCH_ROWS(a,b) (fetchRows(a,b,__LINE__))
#define WIPE_ROWS(a) (RUN_SQL(a, "DELETE FROM BASICS", PGRES_COMMAND_OK))
-/// @brief Verifies that PgResultSet row and colum meta-data is correct
+/// @brief Verifies that PgResultSet row and column meta-data is correct
TEST_F(PgSqlBasicsTest, rowColumnBasics) {
// We fetch the table contents, which at this point should be no rows.
PgSqlResultPtr r;
FETCH_ROWS(r, 0);
- // Column meta-data is deteremined by the select statement and is
+ // Column meta-data is determined by the select statement and is
// present whether or not any rows were returned.
EXPECT_EQ(r->getCols(), NUM_BASIC_COLS);
diff --git a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
index 4f7b794887..f9aae9d93d 100644
--- a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
+++ b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -87,6 +87,48 @@ public:
hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
}
+ /// @brief returns number of rows in a table
+ ///
+ /// Note: This method uses its own connection. It will not work if your test
+ /// uses transactions.
+ ///
+ /// @param name of the table
+ /// @return number of rows currently present in the table
+ int countRowsInTable(const std::string& table) {
+ string query = "SELECT * FROM " + table;
+
+ PgSqlConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+
+ PgSqlConnection conn(params);
+ conn.openDatabase();
+
+ PgSqlResult r(PQexec(conn, query.c_str()));
+ if (PQresultStatus(r) != PGRES_TUPLES_OK) {
+ isc_throw(DbOperationError, "Query failed:" << PQerrorMessage(conn));
+ }
+
+ int numrows = PQntuples(r);
+ return (numrows);
+ }
+
+ /// @brief Returns number of IPv4 options in the DB table.
+ virtual int countDBOptions4() {
+ return (countRowsInTable("dhcp4_options"));
+ }
+
+ /// @brief Returns number of IPv4 options in the DB table.
+ virtual int countDBOptions6() {
+ return (countRowsInTable("dhcp6_options"));
+ }
+
+ /// @brief Returns number of IPv6 reservations in the DB table.
+ virtual int countDBReservations6() {
+ return (countRowsInTable("ipv6_reservations"));
+ }
+
};
/// @brief Check that database can be opened
@@ -221,6 +263,12 @@ TEST_F(PgSqlHostDataSourceTest, get4ByCircuitId) {
testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
}
+// Test verifies if a host reservation can be added and later retrieved by
+// client-id.
+TEST_F(PgSqlHostDataSourceTest, get4ByClientId) {
+ testGet4ByIdentifier(Host::IDENT_CLIENT_ID);
+}
+
// Test verifies if hardware address and client identifier are not confused.
TEST_F(PgSqlHostDataSourceTest, hwaddrNotClientId1) {
testHWAddrNotClientId();
@@ -487,4 +535,31 @@ TEST_F(PgSqlHostDataSourceTest, messageFields) {
testMessageFields4();
}
+// Check that delete(subnet-id, addr4) works.
+TEST_F(PgSqlHostDataSourceTest, deleteByAddr4) {
+ testDeleteByAddr4();
+}
+
+// Check that delete(subnet4-id, identifier-type, identifier) works.
+TEST_F(PgSqlHostDataSourceTest, deleteById4) {
+ testDeleteById4();
+}
+
+// Check that delete(subnet4-id, identifier-type, identifier) works,
+// even when options are present.
+TEST_F(PgSqlHostDataSourceTest, deleteById4Options) {
+ testDeleteById4Options();
+}
+
+// Check that delete(subnet6-id, identifier-type, identifier) works.
+TEST_F(PgSqlHostDataSourceTest, deleteById6) {
+ testDeleteById6();
+}
+
+// Check that delete(subnet6-id, identifier-type, identifier) works,
+// even when options are present.
+TEST_F(PgSqlHostDataSourceTest, deleteById6Options) {
+ testDeleteById6Options();
+}
+
}; // Of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc
index 0a629d5579..6b59fcdb2c 100644
--- a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -108,7 +108,7 @@ TEST(PgSqlOpenTest, OpenDatabase) {
// Check that lease manager open the database opens correctly with a longer
// timeout. If it fails, print the error message.
try {
- string connection_string = validPgSQLConnectionString() + string(" ") +
+ string connection_string = validPgSQLConnectionString() + string(" ") +
string(VALID_TIMEOUT);
LeaseMgrFactory::create(connection_string);
EXPECT_NO_THROW((void) LeaseMgrFactory::instance());
@@ -190,8 +190,8 @@ TEST_F(PgSqlLeaseMgrTest, checkVersion) {
// Check version
pair<uint32_t, uint32_t> version;
ASSERT_NO_THROW(version = lmptr_->getVersion());
- EXPECT_EQ(PG_CURRENT_VERSION, version.first);
- EXPECT_EQ(PG_CURRENT_MINOR, version.second);
+ EXPECT_EQ(PG_SCHEMA_VERSION_MAJOR, version.first);
+ EXPECT_EQ(PG_SCHEMA_VERSION_MINOR, version.second);
}
////////////////////////////////////////////////////////////////////////////////
@@ -412,4 +412,14 @@ TEST_F(PgSqlLeaseMgrTest, recountLeaseStats6) {
testRecountLeaseStats6();
}
+// Tests that leases from specific subnet can be removed.
+TEST_F(PgSqlLeaseMgrTest, DISABLED_wipeLeases4) {
+ testWipeLeases4();
+}
+
+// Tests that leases from specific subnet can be removed.
+TEST_F(PgSqlLeaseMgrTest, DISABLED_wipeLeases6) {
+ testWipeLeases6();
+}
+
}; // namespace
diff --git a/src/lib/dhcpsrv/tests/pool_unittest.cc b/src/lib/dhcpsrv/tests/pool_unittest.cc
index 1ce452fd3b..5447e99156 100644
--- a/src/lib/dhcpsrv/tests/pool_unittest.cc
+++ b/src/lib/dhcpsrv/tests/pool_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -112,7 +112,7 @@ TEST(Pool4Test, unique_id) {
}
// Simple check if toText returns reasonable values
-TEST(Pool4Test,toText) {
+TEST(Pool4Test, toText) {
Pool4 pool1(IOAddress("192.0.2.7"), IOAddress("192.0.2.17"));
EXPECT_EQ("type=V4, 192.0.2.7-192.0.2.17", pool1.toText());
@@ -120,6 +120,58 @@ TEST(Pool4Test,toText) {
EXPECT_EQ("type=V4, 192.0.2.128-192.0.2.143", pool2.toText());
}
+// This test checks that it is possible to specify pool specific options.
+TEST(Pool4Test, addOptions) {
+ // Create a pool to add options to it.
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.255")));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, "dhcp4"));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp4 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, "isc"));
+ }
+
+ // Get options from the pool and check if all 10 are there.
+ OptionContainerPtr options = pool->getCfgOption()->getAll("dhcp4");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // Validate codes of options added to dhcp4 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ options = pool->getCfgOption()->getAll("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = pool->getCfgOption()->getAll("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
TEST(Pool6Test, constructor_first_last) {
// let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool
@@ -329,7 +381,7 @@ TEST(Pool6Test, unique_id) {
}
// Simple check if toText returns reasonable values
-TEST(Pool6Test,toText) {
+TEST(Pool6Test, toText) {
Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8::1"),
IOAddress("2001:db8::2"));
EXPECT_EQ("type=IA_NA, 2001:db8::1-2001:db8::2, delegated_len=128",
diff --git a/src/lib/dhcpsrv/tests/srv_config_unittest.cc b/src/lib/dhcpsrv/tests/srv_config_unittest.cc
index 1890157f0a..cd664b666d 100644
--- a/src/lib/dhcpsrv/tests/srv_config_unittest.cc
+++ b/src/lib/dhcpsrv/tests/srv_config_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/srv_config.h>
#include <dhcpsrv/subnet.h>
+#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
using namespace isc::asiolink;
@@ -50,10 +51,10 @@ public:
test_subnets4_.push_back(subnet);
}
// Create IPv6 subnets.
+ IOAddress prefix("2001:db8:1::");
for (int i = 0; i < TEST_SUBNETS_NUM; ++i) {
// This is a base prefix. All other prefixes will be created by
// modifying this one.
- IOAddress prefix("2001:db8:1::0");
std::vector<uint8_t> prefix_bytes = prefix.toBytes();
// Modify 5th byte of the prefix, so 2001:db8:1::0 becomes
// 2001:db8:2::0 etc.
@@ -64,9 +65,9 @@ public:
}
// Build our reference dictionary of client classes
- ref_dictionary_->addClass("cc1", ExpressionPtr(), CfgOptionPtr());
- ref_dictionary_->addClass("cc2", ExpressionPtr(), CfgOptionPtr());
- ref_dictionary_->addClass("cc3", ExpressionPtr(), CfgOptionPtr());
+ ref_dictionary_->addClass("cc1", ExpressionPtr(), "", CfgOptionPtr());
+ ref_dictionary_->addClass("cc2", ExpressionPtr(), "", CfgOptionPtr());
+ ref_dictionary_->addClass("cc3", ExpressionPtr(), "", CfgOptionPtr());
}
@@ -141,8 +142,9 @@ SrvConfigTest::addSubnet6(const unsigned int index) {
void
SrvConfigTest::enableDDNS(const bool enable) {
- // D2 configuration should always be non-NULL.
- CfgMgr::instance().getD2ClientConfig()->enableUpdates(enable);
+ const D2ClientConfigPtr& d2_config = conf_.getD2ClientConfig();
+ ASSERT_TRUE(d2_config);
+ d2_config->enableUpdates(enable);
}
// Check that by default there are no logging entries
@@ -237,7 +239,6 @@ TEST_F(SrvConfigTest, summarySubnets) {
addSubnet6(1);
EXPECT_EQ("added IPv4 subnets: 2; added IPv6 subnets: 2",
conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
-
}
// Verifies that we can get and set the client class dictionary
@@ -260,6 +261,27 @@ TEST_F(SrvConfigTest, classDictionaryBasics) {
EXPECT_EQ(ref_dictionary_->getClasses()->size(), cd->getClasses()->size());
}
+// This test verifies that RFC6842 (echo client-id) compatibility may be
+// configured.
+TEST_F(SrvConfigTest, echoClientId) {
+ SrvConfig conf;
+
+ // Check that the default is true
+ EXPECT_TRUE(conf.getEchoClientId());
+
+ // Check that it can be modified to false
+ conf.setEchoClientId(false);
+ EXPECT_FALSE(conf.getEchoClientId());
+
+ // Check that the default value can be restored
+ conf.setEchoClientId(true);
+ EXPECT_TRUE(conf.getEchoClientId());
+
+ // Check the other constructor has the same default
+ SrvConfig conf1(1);
+ EXPECT_TRUE(conf1.getEchoClientId());
+}
+
// This test checks if entire configuration can be copied and that the sequence
// number is not affected.
TEST_F(SrvConfigTest, copy) {
@@ -378,4 +400,89 @@ TEST_F(SrvConfigTest, equality) {
EXPECT_FALSE(conf1 != conf2);
}
+// Verifies that we can get and set configured hooks libraries
+TEST_F(SrvConfigTest, hooksLibraries) {
+ SrvConfig conf(32);
+ isc::hooks::HooksConfig& libraries = conf.getHooksConfig();
+
+ // Upon construction configured hooks libraries should be empty.
+ EXPECT_EQ(0, libraries.get().size());
+
+ // Verify we can update it.
+ isc::data::ConstElementPtr elem0;
+ libraries.add("foo", elem0);
+ std::string config = "{ \"library\": \"bar\" }";
+ isc::data::ConstElementPtr elem1 = isc::data::Element::fromJSON(config);
+ libraries.add("bar", elem1);
+ EXPECT_EQ(2, libraries.get().size());
+ EXPECT_EQ(2, conf.getHooksConfig().get().size());
+
+ // Try to copy
+ SrvConfig copied(64);
+ ASSERT_TRUE(conf != copied);
+ ASSERT_NO_THROW(conf.copy(copied));
+ ASSERT_TRUE(conf == copied);
+ EXPECT_EQ(2, copied.getHooksConfig().get().size());
+
+ EXPECT_TRUE(copied.getHooksConfig().equal(conf.getHooksConfig()));
+}
+
+// Verifies that the toElement method works well (tests limited to
+// direct parameters)
+TEST_F(SrvConfigTest, unparse) {
+ SrvConfig conf(32);
+ std::string header4 = "{\n\"Dhcp4\": {\n";
+ std::string header6 = "{\n\"Dhcp6\": {\n";
+
+ std::string defaults = "\"decline-probation-period\": 0,\n";
+ defaults += "\"dhcp4o6-port\": 0,\n";
+ defaults += "\"interfaces-config\": { \"interfaces\": [ ],\n";
+ defaults += " \"re-detect\": false },\n";
+ defaults += "\"option-def\": [ ],\n";
+ defaults += "\"option-data\": [ ],\n";
+ defaults += "\"expired-leases-processing\": ";
+ defaults += conf.getCfgExpiration()->toElement()->str() + ",\n";
+ defaults += "\"lease-database\": { \"type\": \"memfile\" },\n";
+ defaults += "\"hooks-libraries\": [ ],\n";
+ defaults += "\"dhcp-ddns\": \n";
+ defaults += conf.getD2ClientConfig()->toElement()->str() + ",\n";
+
+ std::string defaults4 = "\"echo-client-id\": true,\n";
+ defaults4 += "\"subnet4\": [ ],\n";
+ defaults4 += "\"host-reservation-identifiers\": ";
+ defaults4 += "[ \"hw-address\", \"duid\", \"circuit-id\", \"client-id\" ],\n";
+
+ std::string defaults6 = "\"relay-supplied-options\": [ \"65\" ],\n";
+ defaults6 += "\"subnet6\": [ ],\n";
+ defaults6 += "\"server-id\": ";
+ defaults6 += conf.getCfgDUID()->toElement()->str() + ",\n";
+ defaults6 += "\"host-reservation-identifiers\": ";
+ defaults6 += "[ \"hw-address\", \"duid\" ],\n";
+ defaults6 += "\"dhcp4o6-port\": 0,\n";
+ defaults6 += "\"mac-sources\": [ \"any\" ]\n";
+
+ std::string params = "\"echo-client-id\": true,\n";
+ params += "\"dhcp4o6-port\": 0\n";
+ std::string trailer = "}\n}\n";
+
+ // Verify DHCPv4
+ CfgMgr::instance().setFamily(AF_INET);
+ isc::test::runToElementTest<SrvConfig>
+ (header4 + defaults + defaults4 + params + trailer, conf);
+
+ // Verify DHCPv6
+ CfgMgr::instance().setFamily(AF_INET6);
+ isc::test::runToElementTest<SrvConfig>
+ (header6 + defaults + defaults6 + trailer, conf);
+
+ // Verify direct non-default parameters
+ CfgMgr::instance().setFamily(AF_INET);
+ conf.setEchoClientId(false);
+ conf.setDhcp4o6Port(6767);
+ params = "\"echo-client-id\": false,\n";
+ params += "\"dhcp4o6-port\": 6767\n";
+ isc::test::runToElementTest<SrvConfig>
+ (header4 + defaults + defaults4 + params + trailer, conf);
+}
+
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 00277013fb..eb24713d66 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -293,13 +293,15 @@ TEST(Subnet4Test, clientClasses) {
three_classes.insert("baz");
// No class restrictions defined, any client should be supported
+ EXPECT_EQ(0, subnet->getClientClasses().size());
EXPECT_TRUE(subnet->clientSupported(no_class));
EXPECT_TRUE(subnet->clientSupported(foo_class));
EXPECT_TRUE(subnet->clientSupported(bar_class));
EXPECT_TRUE(subnet->clientSupported(three_classes));
- // Let's allow only clients belongning to "bar" class.
+ // Let's allow only clients belonging to "bar" class.
subnet->allowClientClass("bar");
+ EXPECT_EQ(1, subnet->getClientClasses().size());
EXPECT_FALSE(subnet->clientSupported(no_class));
EXPECT_FALSE(subnet->clientSupported(foo_class));
@@ -325,13 +327,15 @@ TEST(Subnet4Test, clientClassesMultiple) {
bar_class.insert("bar");
// No class restrictions defined, any client should be supported
+ EXPECT_EQ(0, subnet->getClientClasses().size());
EXPECT_TRUE(subnet->clientSupported(no_class));
EXPECT_TRUE(subnet->clientSupported(foo_class));
EXPECT_TRUE(subnet->clientSupported(bar_class));
- // Let's allow clients belongning to "bar" or "foo" class.
+ // Let's allow clients belonging to "bar" or "foo" class.
subnet->allowClientClass("bar");
subnet->allowClientClass("foo");
+ EXPECT_EQ(2, subnet->getClientClasses().size());
// Class-less clients are to be rejected.
EXPECT_FALSE(subnet->clientSupported(no_class));
@@ -462,7 +466,7 @@ TEST(Subnet4Test, PoolType) {
EXPECT_EQ(pool1, subnet->getPool(Lease::TYPE_V4, IOAddress("192.2.1.5")));
EXPECT_EQ(pool2, subnet->getPool(Lease::TYPE_V4, IOAddress("192.2.2.254")));
- // Try with bogus hints (hints should be ingored)
+ // Try with bogus hints (hints should be ignored)
EXPECT_EQ(pool1, subnet->getPool(Lease::TYPE_V4, IOAddress("10.1.1.1")));
// Trying to add Pool6 to Subnet4 is a big no,no!
@@ -694,7 +698,7 @@ TEST(Subnet6Test, poolTypes) {
EXPECT_EQ(pool2, subnet->getPool(Lease::TYPE_TA, IOAddress("2001:db8:1:2::1")));
EXPECT_EQ(pool3, subnet->getPool(Lease::TYPE_PD, IOAddress("2001:db8:1:3::1")));
- // Try with bogus hints (hints should be ingored)
+ // Try with bogus hints (hints should be ignored)
EXPECT_EQ(pool1, subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1:7::1")));
EXPECT_EQ(pool2, subnet->getPool(Lease::TYPE_TA, IOAddress("2001:db8:1:7::1")));
EXPECT_EQ(pool3, subnet->getPool(Lease::TYPE_PD, IOAddress("2001:db8:1:7::1")));
@@ -740,13 +744,15 @@ TEST(Subnet6Test, clientClasses) {
three_classes.insert("baz");
// No class restrictions defined, any client should be supported
+ EXPECT_EQ(0, subnet->getClientClasses().size());
EXPECT_TRUE(subnet->clientSupported(no_class));
EXPECT_TRUE(subnet->clientSupported(foo_class));
EXPECT_TRUE(subnet->clientSupported(bar_class));
EXPECT_TRUE(subnet->clientSupported(three_classes));
- // Let's allow only clients belongning to "bar" class.
+ // Let's allow only clients belonging to "bar" class.
subnet->allowClientClass("bar");
+ EXPECT_EQ(1, subnet->getClientClasses().size());
EXPECT_FALSE(subnet->clientSupported(no_class));
EXPECT_FALSE(subnet->clientSupported(foo_class));
@@ -772,13 +778,15 @@ TEST(Subnet6Test, clientClassesMultiple) {
bar_class.insert("bar");
// No class restrictions defined, any client should be supported
+ EXPECT_EQ(0, subnet->getClientClasses().size());
EXPECT_TRUE(subnet->clientSupported(no_class));
EXPECT_TRUE(subnet->clientSupported(foo_class));
EXPECT_TRUE(subnet->clientSupported(bar_class));
- // Let's allow only clients belongning to "foo" or "bar" class.
+ // Let's allow only clients belonging to "foo" or "bar" class.
subnet->allowClientClass("foo");
subnet->allowClientClass("bar");
+ EXPECT_EQ(2, subnet->getClientClasses().size());
// Class-less clients are to be rejected.
EXPECT_FALSE(subnet->clientSupported(no_class));
@@ -936,11 +944,9 @@ TEST(Subnet6Test, addNonUniqueOptions) {
// Look for the codes 100-109.
for (uint16_t code = 100; code < 110; ++ code) {
// For each code we should get two instances of options->
- std::pair<OptionContainerTypeIndex::const_iterator,
- OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(code);
+ OptionContainerTypeRange range = idx.equal_range(code);
// Distance between iterators indicates how many options
- // have been retured for the particular code.
+ // have been returned for the particular code.
ASSERT_EQ(2, distance(range.first, range.second));
// Check that returned options actually have the expected option code.
for (OptionContainerTypeIndex::const_iterator option_desc = range.first;
@@ -952,9 +958,7 @@ TEST(Subnet6Test, addNonUniqueOptions) {
// Let's try to find some non-exiting option.
const uint16_t non_existing_code = 150;
- std::pair<OptionContainerTypeIndex::const_iterator,
- OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(non_existing_code);
+ OptionContainerTypeRange range = idx.equal_range(non_existing_code);
// Empty set is expected.
EXPECT_EQ(0, distance(range.first, range.second));
}
@@ -986,17 +990,13 @@ TEST(Subnet6Test, addPersistentOption) {
OptionContainerPersistIndex& idx = options->get<2>();
// Get all persistent options->
- std::pair<OptionContainerPersistIndex::const_iterator,
- OptionContainerPersistIndex::const_iterator> range_persistent =
- idx.equal_range(true);
- // 3 out of 10 options have been flagged persistent.
+ OptionContainerPersistRange range_persistent = idx.equal_range(true);
+ // 7 out of 10 options have been flagged persistent.
ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
// Get all non-persistent options->
- std::pair<OptionContainerPersistIndex::const_iterator,
- OptionContainerPersistIndex::const_iterator> range_non_persistent =
- idx.equal_range(false);
- // 7 out of 10 options have been flagged persistent.
+ OptionContainerPersistRange range_non_persistent = idx.equal_range(false);
+ // 3 out of 10 options have been flagged not persistent.
ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
}
@@ -1088,7 +1088,7 @@ TEST(Subnet6Test, inRangeinPool) {
IOAddress("2001:db8::20")));
subnet->addPool(pool1);
- // 192.1.1.1 belongs to the subnet...
+ // 2001:db8::1 belongs to the subnet...
EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1")));
// ... but it does not belong to any pool within
EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::1")));
@@ -1114,6 +1114,40 @@ TEST(Subnet6Test, inRangeinPool) {
EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::21")));
}
+// This test verifies that inRange() and inPool() methods work properly
+// for prefixes too.
+TEST(Subnet6Test, PdinRangeinPool) {
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 64, 1, 2, 3, 4));
+
+ // this one is in subnet
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"),
+ 96, 112));
+ subnet->addPool(pool1);
+
+ // this one is not in subnet
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"),
+ 96, 112));
+ subnet->addPool(pool2);
+
+ // 2001:db8::1:0:0 belongs to the subnet...
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1:0:0")));
+ // ... but it does not belong to any pool within
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_PD, IOAddress("2001:db8::1:0:0")));
+
+ // 2001:db8:1::1 does not belong to the subnet...
+ EXPECT_FALSE(subnet->inRange(IOAddress("2001:db8:1::1")));
+ // ... but it belongs to the second pool
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_PD, IOAddress("2001:db8:1::1")));
+
+ // 2001:db8::1 belongs to the subnet and to the first pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_PD, IOAddress("2001:db8::1")));
+
+ // 2001:db8:0:1:0:1:: does not belong to the subnet and any pool
+ EXPECT_FALSE(subnet->inRange(IOAddress("2001:db8:0:1:0:1::")));
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_PD, IOAddress("2001:db8:0:1:0:1::")));
+}
+
// This test checks if the toText() method returns text representation
TEST(Subnet6Test, toText) {
Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4);
diff --git a/src/lib/dhcpsrv/tests/test_get_callout_handle.h b/src/lib/dhcpsrv/tests/test_get_callout_handle.h
index 1658933d1d..7dee3f2ed8 100644
--- a/src/lib/dhcpsrv/tests/test_get_callout_handle.h
+++ b/src/lib/dhcpsrv/tests/test_get_callout_handle.h
@@ -21,7 +21,7 @@ namespace test {
/// ensure that the getCalloutHandle() template function is referred to by
/// two separate compilation units, and so test that data stored in one unit
/// can be accessed by another. (This should be the case, but some compilers
-/// mabe be odd when it comes to template instantiation.)
+/// maybe be odd when it comes to template instantiation.)
///
/// @param pktptr Pointer to a Pkt6 object.
///
diff --git a/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc b/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc
index 9e606a61e3..43ef1ac935 100644
--- a/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,10 +6,10 @@
#include <config.h>
#include <asiolink/asio_wrapper.h>
-#include <dhcp/iface_mgr.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
#include <dhcpsrv/timer_mgr.h>
#include <exceptions/exceptions.h>
-#include <util/stopwatch.h>
#include <boost/bind.hpp>
#include <gtest/gtest.h>
@@ -88,6 +88,9 @@ public:
/// timers.
typedef std::map<std::string, unsigned int> CallsCount;
+ /// @brief Pointer to IO service used by the tests.
+ IOServicePtr io_service_;
+
/// @brief Holds the calls count for test timers.
///
/// The key of this map holds the timer names. The value holds the number
@@ -100,16 +103,14 @@ public:
void
TimerMgrTest::SetUp() {
+ io_service_.reset(new IOService());
timer_mgr_ = TimerMgr::instance();
+ timer_mgr_->setIOService(io_service_);
calls_count_.clear();
- // Make sure there are no dangling threads.
- timer_mgr_->stopThread();
}
void
TimerMgrTest::TearDown() {
- // Make sure there are no dangling threads.
- timer_mgr_->stopThread();
// Remove all timers.
timer_mgr_->unregisterTimers();
}
@@ -129,14 +130,13 @@ TimerMgrTest::registerTimer(const std::string& timer_name, const long timer_inte
}
void
-TimerMgrTest::doWait(const long timeout, const bool call_receive) {
- util::Stopwatch stopwatch;
- while (stopwatch.getTotalMilliseconds() < timeout) {
- if (call_receive) {
- // Block for one 1 millisecond.
- IfaceMgr::instancePtr()->receive6(0, 1000);
- }
- }
+TimerMgrTest::doWait(const long timeout, const bool /*call_receive*/) {
+ IntervalTimer timer(*io_service_);
+ timer.setup([this]() {
+ io_service_->stop();
+ }, timeout, IntervalTimer::ONE_SHOT);
+ io_service_->run();
+ io_service_->get_io_service().reset();
}
void
@@ -181,20 +181,6 @@ TEST_F(TimerMgrTest, registerTimer) {
ASSERT_THROW(timer_mgr_->registerTimer("timer2", makeCallback("timer2"), 1,
IntervalTimer::ONE_SHOT),
BadValue);
-
- // Start worker thread.
- ASSERT_NO_THROW(timer_mgr_->startThread());
-
- // Can't register the timer when the thread is running.
- ASSERT_THROW(timer_mgr_->registerTimer("timer1", makeCallback("timer1"), 1,
- IntervalTimer::ONE_SHOT),
- InvalidOperation);
-
- // Stop the thread and retry.
- ASSERT_NO_THROW(timer_mgr_->stopThread());
- EXPECT_NO_THROW(timer_mgr_->registerTimer("timer1", makeCallback("timer1"), 1,
- IntervalTimer::ONE_SHOT));
-
}
// This test verifies that it is possible to unregister a timer from
@@ -204,20 +190,16 @@ TEST_F(TimerMgrTest, unregisterTimer) {
ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
ASSERT_EQ(1, timer_mgr_->timersCount());
ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
- ASSERT_NO_THROW(timer_mgr_->startThread());
// Wait for the timer to execute several times.
doWait(100);
- // Stop the thread but execute pending callbacks.
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
-
// Remember how many times the timer's callback was executed.
const unsigned int calls_count = calls_count_["timer1"];
ASSERT_GT(calls_count, 0);
// Check that an attempt to unregister a non-existing timer would
- // result in exeception.
+ // result in exception.
ASSERT_THROW(timer_mgr_->unregisterTimer("timer2"), BadValue);
// Number of timers shouldn't have changed.
ASSERT_EQ(1, timer_mgr_->timersCount());
@@ -226,10 +208,7 @@ TEST_F(TimerMgrTest, unregisterTimer) {
ASSERT_NO_THROW(timer_mgr_->unregisterTimer("timer1"));
ASSERT_EQ(0, timer_mgr_->timersCount());
- // Start the thread again and wait another 100ms.
- ASSERT_NO_THROW(timer_mgr_->startThread());
doWait(100);
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
// The number of calls for the timer1 shouldn't change as the
// timer had been unregistered.
@@ -252,10 +231,7 @@ TEST_F(TimerMgrTest, unregisterTimers) {
<< s.str();
}
- // Start worker thread and wait for 500ms.
- ASSERT_NO_THROW(timer_mgr_->startThread());
doWait(500);
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
// Make sure that all timers have been executed at least once.
for (CallsCount::iterator it = calls_count_.begin();
@@ -276,32 +252,13 @@ TEST_F(TimerMgrTest, unregisterTimers) {
// Make sure there are no timers registered.
ASSERT_EQ(0, timer_mgr_->timersCount());
- // Start worker thread again and wait for 500ms.
- ASSERT_NO_THROW(timer_mgr_->startThread());
doWait(500);
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
// The calls counter shouldn't change because there are
// no timers registered.
EXPECT_TRUE(calls_count == calls_count_);
}
-// This test checks that it is not possible to unregister timers
-// while the thread is running.
-TEST_F(TimerMgrTest, unregisterTimerWhileRunning) {
- // Register two timers.
- ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
- ASSERT_NO_FATAL_FAILURE(registerTimer("timer2", 1));
-
- // Start the thread and make sure we can't unregister them.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- EXPECT_THROW(timer_mgr_->unregisterTimer("timer1"), InvalidOperation);
- EXPECT_THROW(timer_mgr_->unregisterTimers(), InvalidOperation);
-
- // No need to stop the thread as it will be stopped by the
- // test fixture destructor.
-}
-
// This test verifies that the timer execution can be cancelled.
TEST_F(TimerMgrTest, cancel) {
// Register timer.
@@ -309,14 +266,13 @@ TEST_F(TimerMgrTest, cancel) {
// Kick in the timer and wait for 500ms.
ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
- ASSERT_NO_THROW(timer_mgr_->startThread());
+
doWait(500);
- ASSERT_NO_THROW(timer_mgr_->stopThread());
- // Cancelling non-existing timer should fail.
+ // Canceling non-existing timer should fail.
EXPECT_THROW(timer_mgr_->cancel("timer2"), BadValue);
- // Cancelling the good one should pass, even when the worker
+ // Canceling the good one should pass, even when the worker
// thread is running.
ASSERT_NO_THROW(timer_mgr_->cancel("timer1"));
@@ -324,10 +280,7 @@ TEST_F(TimerMgrTest, cancel) {
// another 500ms.
unsigned int calls_count = calls_count_["timer1"];
- ASSERT_NO_THROW(timer_mgr_->startThread());
doWait(500);
- // Stop thread before we setup again.
- ASSERT_NO_THROW(timer_mgr_->stopThread());
// The number of calls shouldn't change because the timer had been
// cancelled.
@@ -337,7 +290,6 @@ TEST_F(TimerMgrTest, cancel) {
ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
// Restart the thread.
- ASSERT_NO_THROW(timer_mgr_->startThread());
doWait(500);
// New calls should be recorded.
@@ -357,9 +309,6 @@ TEST_F(TimerMgrTest, scheduleTimers) {
ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
ASSERT_NO_THROW(timer_mgr_->setup("timer2"));
- // We can start the worker thread before we even kick in the timers.
- ASSERT_NO_THROW(timer_mgr_->startThread());
-
// Run IfaceMgr::receive6() in the loop for 1000ms. This function
// will read data from the watch sockets created when the timers
// were registered. The data is delivered to the watch sockets
@@ -368,13 +317,9 @@ TEST_F(TimerMgrTest, scheduleTimers) {
// with the watch sockets should be called.
doWait(1000);
- // Stop the worker thread, which would halt the execution of
- // the timers.
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
-
// We have been running the timer for 1000ms at the interval of
// 50ms. The maximum number of callbacks is 20. However, the
- // callback itself takes time. Stoping the thread takes time.
+ // callback itself takes time. Stopping the thread takes time.
// So, the real number differs significantly. We don't know
// exactly how many have been executed. It should be more
// than 10 for sure. But we really made up the numbers here.
@@ -394,9 +339,6 @@ TEST_F(TimerMgrTest, scheduleTimers) {
// Unregister the 'timer1'.
ASSERT_NO_THROW(timer_mgr_->unregisterTimer("timer1"));
- // Restart the thread.
- ASSERT_NO_THROW(timer_mgr_->startThread());
-
// Wait another 500ms. The 'timer1' was unregistered so it
// should not make any more calls. The 'timer2' should still
// work as previously.
@@ -408,48 +350,6 @@ TEST_F(TimerMgrTest, scheduleTimers) {
EXPECT_GT(calls_count_["timer2"], calls_count_timer2);
}
-// This test verifies that it is possible to force that the pending
-// timer callbacks are executed when the worker thread is stopped.
-TEST_F(TimerMgrTest, stopThreadWithRunningHandlers) {
- // Register 'timer1'.
- ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
-
- // Kick in the timer.
- ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
- ASSERT_NO_THROW(timer_mgr_->startThread());
-
- // Run the thread for 100ms. This should run some timers. The 'false'
- // value indicates that the IfaceMgr::receive6 is not called, so the
- // watch socket is never cleared.
- doWait(100, false);
-
- // There should be no calls registered for the timer1.
- EXPECT_EQ(0, calls_count_["timer1"]);
-
- // Stop the worker thread without completing pending callbacks.
- ASSERT_NO_THROW(timer_mgr_->stopThread(false));
-
- // There should be still not be any calls registered.
- EXPECT_EQ(0, calls_count_["timer1"]);
-
- // We can restart the worker thread before we even kick in the timers.
- ASSERT_NO_THROW(timer_mgr_->startThread());
-
- // Run the thread for 100ms. This should run some timers. The 'false'
- // value indicates that the IfaceMgr::receive6 is not called, so the
- // watch socket is never cleared.
- doWait(100, false);
-
- // There should be no calls registered for the timer1.
- EXPECT_EQ(0, calls_count_["timer1"]);
-
- // Stop the worker thread with completing pending callbacks.
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
-
- // There should be one call registered.
- EXPECT_EQ(1, calls_count_["timer1"]);
-}
-
// This test verifies that exceptions emitted from the callback would
// be handled by the TimerMgr.
TEST_F(TimerMgrTest, callbackWithException) {
@@ -464,9 +364,14 @@ TEST_F(TimerMgrTest, callbackWithException) {
// Start thread. We hope that exception will be caught by the @c TimerMgr
// and will not kill the process.
- ASSERT_NO_THROW(timer_mgr_->startThread());
doWait(500);
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
+}
+
+// This test verifies that IO service specified for the TimerMgr
+// must not be null.
+TEST_F(TimerMgrTest, setIOService) {
+ EXPECT_THROW(timer_mgr_->setIOService(IOServicePtr()),
+ BadValue);
}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/testutils/config_result_check.cc b/src/lib/dhcpsrv/testutils/config_result_check.cc
index e36aac7cce..15b6e87856 100644
--- a/src/lib/dhcpsrv/testutils/config_result_check.cc
+++ b/src/lib/dhcpsrv/testutils/config_result_check.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -40,7 +40,7 @@ bool errorContainsPosition(ConstElementPtr error_element,
// If the file name is present, check that it is followed by the line
// number and position within the line.
if (pos != std::string::npos) {
- // Split the string starting at the begining of the <filename>. It
+ // Split the string starting at the beginning of the <filename>. It
// should return a vector of strings.
std::string sub = error_string.substr(pos);
std::vector<std::string> split_pos;
diff --git a/src/lib/dhcpsrv/testutils/config_result_check.h b/src/lib/dhcpsrv/testutils/config_result_check.h
index 6aceaf0d58..29d4477a11 100644
--- a/src/lib/dhcpsrv/testutils/config_result_check.h
+++ b/src/lib/dhcpsrv/testutils/config_result_check.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -18,7 +18,7 @@ namespace test {
///
/// This function checks that the error string returned by the configuration
/// parsers contains the position of the element which caused an error. The
-/// error string is expected to contain at least one occurence of the following:
+/// error string is expected to contain at least one occurrence of the following:
///
/// @code
/// [filename]:[linenum]:[pos]
diff --git a/src/lib/dhcpsrv/timer_mgr.cc b/src/lib/dhcpsrv/timer_mgr.cc
index 54ec50fb9b..f2fe5269bb 100644
--- a/src/lib/dhcpsrv/timer_mgr.cc
+++ b/src/lib/dhcpsrv/timer_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,13 +7,9 @@
#include <config.h>
#include <asiolink/asio_wrapper.h>
#include <asiolink/io_service.h>
-#include <dhcp/iface_mgr.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/timer_mgr.h>
#include <exceptions/exceptions.h>
-#include <util/threads/sync.h>
-#include <util/threads/thread.h>
-#include <util/watch_socket.h>
#include <boost/bind.hpp>
@@ -21,57 +17,15 @@
using namespace isc;
using namespace isc::asiolink;
-using namespace isc::util;
-using namespace isc::util::thread;
namespace {
-/// @brief Simple RAII object setting value to true while in scope.
-///
-/// This class is useful to temporarly set the value to true and
-/// automatically reset it to false when the object is destroyed
-/// as a result of return or exception.
-class ScopedTrue {
-public:
-
- /// @brief Constructor.
- ///
- /// Sets the boolean value to true.
- ///
- /// @param value reference to the value to be set to true.
- ScopedTrue(bool& value, Mutex& mutex)
- : value_(value), mutex_(mutex) {
- Mutex::Locker lock(mutex_);
- value_ = true;
- }
-
- /// @brief Destructor.
- ///
- /// Sets the value to false.
- ~ScopedTrue() {
- Mutex::Locker lock(mutex_);
- value_ = false;
- }
-
-private:
-
- /// @brief Reference to the controlled value.
- bool& value_;
-
- /// @brief Mutex to be used to lock while performing write
- /// operations.
- Mutex& mutex_;
-};
-
/// @brief Structure holding information for a single timer.
///
/// This structure holds the instance of the watch socket being used to
/// signal that the timer is "ready". It also holds the instance of the
/// interval timer and other parameters pertaining to it.
struct TimerInfo {
- /// @brief Instance of the watch socket.
- util::WatchSocket watch_socket_;
-
/// @brief Instance of the interval timer.
asiolink::IntervalTimer interval_timer_;
@@ -97,8 +51,7 @@ struct TimerInfo {
const asiolink::IntervalTimer::Callback& user_callback,
const long interval,
const asiolink::IntervalTimer::Mode& mode)
- : watch_socket_(),
- interval_timer_(io_service),
+ : interval_timer_(io_service),
user_callback_(user_callback),
interval_(interval),
scheduling_mode_(mode) { };
@@ -123,12 +76,12 @@ public:
/// @brief Constructor.
TimerMgrImpl();
- /// @brief Returns a reference to IO service used by the @c TimerMgr.
- asiolink::IOService& getIOService() const {
- return (*io_service_);
- }
+ /// @brief Sets IO service to be used by the Timer Manager.
+ ///
+ /// @param io_service Pointer to the new IO service.
+ void setIOService(const IOServicePtr& io_service);
- /// @brief Registers new timers in the @c TimerMgr.
+ /// @brief Registers new timer in the @c TimerMgr.
///
/// @param timer_name Unique name for the timer.
/// @param callback Pointer to the callback function to be invoked
@@ -139,7 +92,6 @@ public:
/// @c asiolink::IntervalTimer::Mode.
///
/// @throw BadValue if the timer name is invalid or duplicate.
- /// @throw InvalidOperation if the worker thread is running.
void registerTimer(const std::string& timer_name,
const asiolink::IntervalTimer::Callback& callback,
const long interval,
@@ -148,9 +100,8 @@ public:
/// @brief Unregisters specified timer.
///
- /// This method cancels the timer if it is setup. It removes the external
- /// socket from the @c IfaceMgr and closes it. It finally removes the
- /// timer from the internal collection of timers.
+ /// This method cancels the timer if it is setup and removes the timer
+ /// from the internal collection of timers.
///
/// @param timer_name Name of the timer to be unregistered.
///
@@ -187,127 +138,32 @@ public:
/// @throw BadValue if the timer hasn't been registered.
void cancel(const std::string& timer_name);
- /// @brief Checks if the thread is running.
- ///
- /// @return true if the thread is running.
- bool threadRunning() const;
-
- /// @brief Starts thread.
- void createThread();
-
- /// @brief Stops thread gracefully.
- ///
- /// This methods unblocks worker thread if it is blocked waiting for
- /// any handlers and stops it. Outstanding handlers are later executed
- /// in the main thread and all watch sockets are cleared.
- ///
- /// @param run_pending_callbacks Indicates if the pending callbacks
- /// should be executed (if true).
- void stopThread(const bool run_pending_callbacks);
-
private:
- /// @name Internal callbacks.
- //@{
- ///
/// @brief Callback function to be executed for each interval timer when
/// its scheduled interval elapses.
///
- /// This method marks the @c util::Watch socket associated with the
- /// timer as ready (writes data to a pipe). This method will BLOCK until
- /// @c TimerMgrImpl::ifaceMgrCallback is executed from the main thread by
- /// the @c IfaceMgr.
- ///
- /// @param timer_name Unique timer name to be passed to the callback.
- void timerCallback(const std::string& timer_name);
-
- /// @brief Callback function installed on the @c IfaceMgr and associated
- /// with the particular timer.
- ///
- /// This callback function is executed by the @c IfaceMgr when the data
- /// over the specific @c util::WatchSocket is received. This method clears
- /// the socket (reads the data from the pipe) and executes the callback
- /// supplied when the timer was registered.
- ///
/// @param timer_name Unique timer name.
- void ifaceMgrCallback(const std::string& timer_name);
-
- //@}
-
- /// @name Methods to handle ready sockets.
- //@{
- ///
- /// @brief Clear ready sockets and optionally run callbacks.
- ///
- /// This method is called by the @c TimerMgr::stopThread method
- /// to clear watch sockets which may be marked as ready. It will
- /// also optionally run callbacks installed for the timers which
- /// marked sockets as ready.
- ///
- /// @param run_pending_callbacks Indicates if the callbacks should
- /// be executed for the sockets being cleared (if true).
- void clearReadySockets(const bool run_pending_callbacks);
-
- /// @brief Clears a socket and optionally runs a callback.
- ///
- /// This method clears the ready socket pointed to by the specified
- /// iterator. If the @c run_callback is set, the callback will
- /// also be executed.
- ///
- /// @param timer_info_iterator Iterator pointing to the timer
- /// configuration structure.
- /// @param run_callback Boolean value indicating if the callback
- /// should be executed for the socket being cleared (if true).
- ///
- /// @tparam Iterator Iterator pointing to the timer configuration
- /// structure.
- template<typename Iterator>
- void handleReadySocket(Iterator timer_info_iterator,
- const bool run_callback);
-
- //@}
-
- /// @brief Blocking wait for the socket to be cleared.
- void waitForSocketClearing(WatchSocket& watch_socket);
-
- /// @brief Signals that a watch socket has been cleared.
- void signalSocketClearing();
-
- /// @brief Pointer to the @c IfaceMgr.
- IfaceMgrPtr iface_mgr_;
+ void timerCallback(const std::string& timer_name);
/// @brief Pointer to the io service.
asiolink::IOServicePtr io_service_;
- /// @brief Pointer to the worker thread.
- ///
- /// This is initially set to NULL until the thread is started using the
- /// @c TimerMgr::startThread. The @c TimerMgr::stopThread sets it back
- /// to NULL.
- boost::shared_ptr<util::thread::Thread> thread_;
-
- /// @brief Mutex used to synchronize main thread and the worker thread.
- util::thread::Mutex mutex_;
-
- /// @brief Conditional variable used to synchronize main thread and
- /// worker thread.
- util::thread::CondVar cond_var_;
-
- /// @brief Boolean value indicating if the thread is being stopped.
- bool stopping_;
-
- /// @brief Holds mapping of the timer name to the watch socket, timer
- /// instance and other parameters pertaining to the timer.
- ///
- /// Each registered timer has a unique name which is used as a key to
- /// the map. The timer is associated with an instance of the @c WatchSocket
- /// which is marked ready when the interval for the particular elapses.
+ /// @brief Holds mapping of the timer name to timer instance and other
+ /// parameters pertaining to the timer.
TimerInfoMap registered_timers_;
};
TimerMgrImpl::TimerMgrImpl() :
- iface_mgr_(IfaceMgr::instancePtr()), io_service_(new IOService()), thread_(),
- mutex_(), cond_var_(), stopping_(false), registered_timers_() {
+ io_service_(new IOService()), registered_timers_() {
+}
+
+void
+TimerMgrImpl::setIOService(const IOServicePtr& io_service) {
+ if (!io_service) {
+ isc_throw(BadValue, "IO service object must not be null for TimerMgr");
+ }
+ io_service_ = io_service;
}
void
@@ -327,29 +183,12 @@ TimerMgrImpl::registerTimer(const std::string& timer_name,
<< timer_name << "'");
}
- // Must not register new timer when the worker thread is running. Note
- // that worker thread is using IO service and trying to register a new
- // timer while IO service is being used would result in hang.
- if (thread_) {
- isc_throw(InvalidOperation, "unable to register new timer when the"
- " timer manager's worker thread is running");
- }
-
// Create a structure holding the configuration for the timer. It will
- // create the instance if the IntervalTimer and WatchSocket. It will
- // also hold the callback, interval and scheduling mode parameters.
- // This may throw a WatchSocketError if the socket creation fails.
- TimerInfoPtr timer_info(new TimerInfo(getIOService(), callback,
+ // create the instance if the IntervalTimer. It will also hold the
+ // callback, interval and scheduling mode parameters.
+ TimerInfoPtr timer_info(new TimerInfo(*io_service_, callback,
interval, scheduling_mode));
- // Register the WatchSocket in the IfaceMgr and register our own callback
- // to be executed when the data is received over this socket. The only time
- // this may fail is when the socket failed to open which would have caused
- // an exception in the previous call. So we should be safe here.
- iface_mgr_->addExternalSocket(timer_info->watch_socket_.getSelectFd(),
- boost::bind(&TimerMgrImpl::ifaceMgrCallback,
- this, timer_name));
-
// Actually register the timer.
registered_timers_.insert(std::pair<std::string, TimerInfoPtr>(timer_name,
timer_info));
@@ -358,11 +197,6 @@ TimerMgrImpl::registerTimer(const std::string& timer_name,
void
TimerMgrImpl::unregisterTimer(const std::string& timer_name) {
- if (thread_) {
- isc_throw(InvalidOperation, "unable to unregister timer "
- << timer_name << " while worker thread is running");
- }
-
// Find the timer with specified name.
TimerInfoMap::iterator timer_info_it = registered_timers_.find(timer_name);
@@ -375,11 +209,6 @@ TimerMgrImpl::unregisterTimer(const std::string& timer_name) {
// Cancel any pending asynchronous operation and stop the timer.
cancel(timer_name);
- const TimerInfoPtr& timer_info = timer_info_it->second;
-
- // Unregister the watch socket from the IfaceMgr.
- iface_mgr_->deleteExternalSocket(timer_info->watch_socket_.getSelectFd());
-
// Remove the timer.
registered_timers_.erase(timer_info_it);
}
@@ -439,45 +268,6 @@ TimerMgrImpl::cancel(const std::string& timer_name) {
}
// Cancel the timer.
timer_info_it->second->interval_timer_.cancel();
- // Clear watch socket, if ready.
- timer_info_it->second->watch_socket_.clearReady();
-}
-
-
-bool
-TimerMgrImpl::threadRunning() const {
- return (static_cast<bool>(thread_));
-}
-
-void
-TimerMgrImpl::createThread() {
- thread_.reset(new Thread(boost::bind(&IOService::run, &getIOService())));
-}
-
-void
-TimerMgrImpl::stopThread(const bool run_pending_callbacks) {
- // Set the stopping flag to true while we're stopping. This will be
- // automatically reset to false when the function exits or exception
- // is thrown.
- ScopedTrue scoped_true(stopping_, mutex_);
-
- // Stop the IO Service. This will break the IOService::run executed in the
- // worker thread. The thread will now terminate.
- getIOService().stop();
-
- // Some of the watch sockets may be already marked as ready and
- // have some pending callbacks to be executed. If the caller
- // wants us to run the callbacks we clear the sockets and run
- // them. If pending callbacks shouldn't be executed, this will
- // only clear the sockets (which should be substantially faster).
- clearReadySockets(run_pending_callbacks);
- // Wait for the thread to terminate.
- thread_->wait();
- // Set the thread pointer to NULL to indicate that the thread is not running.
- thread_.reset();
- // IO Service has to be reset so as we can call run() on it again if we
- // desire (using the startThread()).
- getIOService().get_io_service().reset();
}
void
@@ -485,74 +275,17 @@ TimerMgrImpl::timerCallback(const std::string& timer_name) {
// Find the specified timer setup.
TimerInfoMap::iterator timer_info_it = registered_timers_.find(timer_name);
if (timer_info_it != registered_timers_.end()) {
- // We will mark watch socket ready - write data to a socket to
- // interrupt the execution of the select() function. This is
- // executed from the worker thread.
- const TimerInfoPtr& timer_info = timer_info_it->second;
-
- // This function is called from the worker thread and we don't want
- // the worker thread to get exceptions out of here. It is unlikely
- // that markReady() would cause any problems but theoretically
- // possible. Hence, we rather log an error and leave.
- try {
- timer_info->watch_socket_.markReady();
-
- } catch (const std::exception& ex) {
- LOG_ERROR(dhcpsrv_logger, DHCPSRV_TIMERMGR_SOCKET_MARK_FAILED)
- .arg(timer_name)
- .arg(ex.what());
-
- // Do not wait for clearing the socket because we were unable
- // to mark it ready anyway.
- return;
- }
-
- // Blocking wait for the socket to be cleared on the other
- // end. This may be interrupted both if the socket is cleared
- // or if the stopThread() has been called on the TimerMgr.
- waitForSocketClearing(timer_info->watch_socket_);
- }
-}
-
-void
-TimerMgrImpl::ifaceMgrCallback(const std::string& timer_name) {
- // Find the specified timer setup.
- TimerInfoMap::iterator timer_info_it = registered_timers_.find(timer_name);
- if (timer_info_it != registered_timers_.end()) {
- // We're executing a callback function from the Interface Manager.
- // This callback function is executed when the call to select() is
- // interrupted as a result of receiving some data over the watch
- // socket. We have to clear the socket which has been marked as
- // ready. Then execute the callback function supplied by the
- // TimerMgr user to perform custom actions on the expiration of
- // the given timer.
- handleReadySocket(timer_info_it, true);
- }
-}
-
-void
-TimerMgrImpl::clearReadySockets(const bool run_pending_callbacks) {
- for (TimerInfoMap::iterator timer_info_it = registered_timers_.begin();
- timer_info_it != registered_timers_.end(); ++timer_info_it) {
- handleReadySocket(timer_info_it, run_pending_callbacks);
- }
-}
-template<typename Iterator>
-void
-TimerMgrImpl::handleReadySocket(Iterator timer_info_iterator,
- const bool run_callback) {
- if (run_callback) {
// Running user-defined operation for the timer. Logging it
// on the slightly lower debug level as there may be many
// such traces.
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION)
- .arg(timer_info_iterator->first);
+ .arg(timer_info_it->first);
std::string error_string;
try {
- timer_info_iterator->second->user_callback_();
+ timer_info_it->second->user_callback_();
} catch (const std::exception& ex){
error_string = ex.what();
@@ -564,46 +297,10 @@ TimerMgrImpl::handleReadySocket(Iterator timer_info_iterator,
// Exception was thrown. Log an error.
if (!error_string.empty()) {
LOG_ERROR(dhcpsrv_logger, DHCPSRV_TIMERMGR_CALLBACK_FAILED)
- .arg(timer_info_iterator->first)
+ .arg(timer_info_it->first)
.arg(error_string);
}
}
-
- try {
- // This shouldn't really fail, but if it does we want to log an
- // error and make sure that the thread is not stuck waiting for
- // the socket to clear.
- timer_info_iterator->second->watch_socket_.clearReady();
-
- } catch (const std::exception& ex) {
- LOG_ERROR(dhcpsrv_logger, DHCPSRV_TIMERMGR_SOCKET_CLEAR_FAILED)
- .arg(timer_info_iterator->first)
- .arg(ex.what());
- }
-
- // Whether it succeeded or not, clear the socket to make sure that
- // the worker thread is not stuck waiting for the main thread.
- signalSocketClearing();
-}
-
-void
-TimerMgrImpl::waitForSocketClearing(WatchSocket& watch_socket) {
- // Waiting for the specific socket to be cleared.
- while (watch_socket.isReady()) {
- Mutex::Locker lock(mutex_);
- // If stopThread has been called there is no sense to further
- // wait for the socket clearing. Leave from here to unblock the
- // worker thread.
- if (stopping_) {
- break;
- }
- cond_var_.wait(mutex_);
- }
-}
-
-void
-TimerMgrImpl::signalSocketClearing() {
- cond_var_.signal();
}
const TimerMgrPtr&
@@ -617,7 +314,6 @@ TimerMgr::TimerMgr()
}
TimerMgr::~TimerMgr() {
- stopThread();
unregisterTimers();
delete impl_;
}
@@ -681,31 +377,8 @@ TimerMgr::cancel(const std::string& timer_name) {
}
void
-TimerMgr::startThread() {
- // Do not start the thread if the thread is already there.
- if (!impl_->threadRunning()) {
- // Only log it if we really start the thread.
- LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
- DHCPSRV_TIMERMGR_START_THREAD);
-
- impl_->createThread();
- }
-}
-
-void
-TimerMgr::stopThread(const bool run_pending_callbacks) {
- // If thread is not running, this is no-op.
- if (impl_->threadRunning()) {
- // Only log it if we really have something to stop.
- LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
- DHCPSRV_TIMERMGR_STOP_THREAD);
-
- impl_->stopThread(run_pending_callbacks);
- }
-}
-IOService&
-TimerMgr::getIOService() const {
- return (impl_->getIOService());
+TimerMgr::setIOService(const IOServicePtr& io_service) {
+ impl_->setIOService(io_service);
}
diff --git a/src/lib/dhcpsrv/timer_mgr.h b/src/lib/dhcpsrv/timer_mgr.h
index 57c2d82a18..ec5b9e3933 100644
--- a/src/lib/dhcpsrv/timer_mgr.h
+++ b/src/lib/dhcpsrv/timer_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -24,13 +24,9 @@ class TimerMgr;
/// @brief Type definition of the shared pointer to @c TimerMgr.
typedef boost::shared_ptr<TimerMgr> TimerMgrPtr;
-/// @brief Manages a pool of asynchronous interval timers for DHCP server.
+/// @brief Manages a pool of asynchronous interval timers.
///
-/// This class holds a pool of asynchronous interval timers which are
-/// capable of interrupting the blocking call to @c select() function in
-/// the main threads of the DHCP servers. The main thread can then
-/// timely execute the callback function associated with the particular
-/// timer.
+/// This class holds a pool of asynchronous interval timers.
///
/// This class is useful for performing periodic actions at the specified
/// intervals, e.g. act upon expired leases (leases reclamation) or
@@ -52,64 +48,9 @@ typedef boost::shared_ptr<TimerMgr> TimerMgrPtr;
/// The registered timer's interval does not begin to elapse until the
/// @c TimerMgr::setup method is called for it.
///
-/// The @c TimerMgr uses worker thread to run the timers. The thread is
-/// started and stopped using the @c TimerMgr::startThread and
-/// @c TimerMgr::stopThread respectively. The thread calls the blocking
-/// @c IOService::run. All the registered timers are associated with
-/// this instance of the @c IOService that the thread is running.
-/// When the timer elapses a generic callback function is executed
-/// @c TimerMgr::timerCallback with the parameter giving the name
-/// of the timer for which it has been executed.
-///
-/// Every registered timer is associated with an instance of the
-/// @c util::WatchSocket object. The socket is registered in the
-/// @c IfaceMgr as an "external" socket. When the generic callback
-/// function is invoked for the timer, it obtains the instance of the
-/// @c util::WatchSocket and marks it "ready". This call effectively
-/// writes the data to a socket (pipe) which interrupts the call
-/// to the @c select() function in the main thread. When the
-/// @c IfaceMgr (in the main thread) detects data transmitted over
-/// the external socket it will invoke a callback function
-/// associated with this socket. This is the
-/// @c TimerMgr::ifaceMgrCallback associated with the socket when the
-/// timer is registered. This callback function is executed in the
-/// main thread. It clears the socket, which unblocks the worker
-/// thread. It also invokes the user callback function specified
-/// for a given timer.
-///
-/// The @c TimerMgr::timerCallback function searches for the
-/// registered timer for which it has been called. This may cause
-/// race conditions between the worker thread and the main thread
-/// if the latter is modifying the collection of the registered
-/// timers. Therefore, the @c TimerMgr does not allow for
-/// registering or unregistering the timers when the worker thread
-/// is running. The worker thread must be stopped first.
-/// It is possible to call @c TimerMgr::setup and @c TimerMgr::cancel
-/// while the worker thread is running but this is considered
-/// unreliable (may cause race conditions) except the case when the
-/// @c TimerMgr::setup is called from the installed callback
-/// function to reschedule the ONE_SHOT timer. This is thread safe
-/// because the worker thread is blocked while the callback function
-/// is executed.
-///
-/// The worker thread is blocked when it executes a generic callback
-/// function in the @c TimerMgr, which marks the watch socket
-/// associated with the elapsed timer as "ready". The thread waits
-/// in the callback function until it is notified by the main thread
-/// (via conditional variable), that one of the watch sockets has
-/// been cleared. It then checks if the main thread cleared the
-/// socket that the worker thread had set. It continues to block
-/// if this was a different socket. It returns (unblocks) otherwise.
-/// The main thread clears the socket when the @c IfaceMgr detects
-/// that this socket has been marked ready by the worker thread.
-/// This is triggered only when the @c IfaceMgr::receive4 or
-/// @c IfaceMgr::receive6 is called. They are called in the main
-/// loops of the DHCP servers, which are also responsible for
-/// processing received packets. Therefore it may take some
-/// time for the main loop to detect that the socket has been
-/// marked ready, call appropriate handler for it and clear it.
-/// In the mean time, the worker thread will remain blocked.
-///
+/// Before the @c TimerMgr can be used the server process must call
+/// @c TimerMgr::setIOService to associate the manager with the IO service
+/// that the server is using to its run tasks.
class TimerMgr : public boost::noncopyable {
public:
@@ -118,20 +59,19 @@ public:
/// @brief Destructor.
///
- /// Stops the worker thread if it is running and unregisteres any
+ /// Stops the worker thread if it is running and unregisters any
/// registered timers.
~TimerMgr();
+ /// @brief Sets IO service to be used by the Timer Manager.
+ ///
+ /// @param io_service Pointer to the new IO service.
+ void setIOService(const asiolink::IOServicePtr& io_service);
+
/// @name Registering, unregistering and scheduling the timers.
//@{
- /// @brief Registers new timers in the @c TimerMgr.
- ///
- /// This method must not be called while the worker thread is running,
- /// as it modifies the internal data structure holding registered
- /// timers, which is also accessed from the worker thread via the
- /// callback. Inserting new element to this data structure and
- /// reading it at the same time would yield undefined behavior.
+ /// @brief Registers new timer in the @c TimerMgr.
///
/// @param timer_name Unique name for the timer.
/// @param callback Pointer to the callback function to be invoked
@@ -142,7 +82,6 @@ public:
/// @c asiolink::IntervalTimer::Mode.
///
/// @throw BadValue if the timer name is invalid or duplicate.
- /// @throw InvalidOperation if the worker thread is running.
void registerTimer(const std::string& timer_name,
const asiolink::IntervalTimer::Callback& callback,
const long interval,
@@ -150,15 +89,8 @@ public:
/// @brief Unregisters specified timer.
///
- /// This method cancels the timer if it is setup. It removes the external
- /// socket from the @c IfaceMgr and closes it. It finally removes the
- /// timer from the internal collection of timers.
- ///
- /// This method must not be called while the worker thread is running,
- /// as it modifies the internal data structure holding registered
- /// timers, which is also accessed from the worker thread via the
- /// callback. Removing element from this data structure and
- /// reading it at the same time would yield undefined behavior.
+ /// This method cancels the timer if it is setup and removes it from the
+ /// internal collection of timers.
///
/// @param timer_name Name of the timer to be unregistered.
///
@@ -197,50 +129,6 @@ public:
//@}
- /// @name Starting and stopping the worker thread.
- //@{
-
- /// @brief Starts worker thread
- ///
- /// This method has no effect if the thread has already been started.
- void startThread();
-
- /// @brief Stops worker thread.
- ///
- /// When the thread is being stopped, it is possible that some of the
- /// timers have elapsed and marked their respective watch sockets
- /// as "ready", but the sockets haven't been yet cleared in the
- /// main thread and the installed callbacks haven't been executed.
- /// It is possible to control whether those pending callbacks should
- /// be executed or not before the call to @c stopThread ends.
- /// If the thread is being stopped as a result of the DHCP server
- /// reconfiguration running pending callback may take significant
- /// amount of time, e.g. when operations on the lease database are
- /// involved. If this is a concern, the function parameter should
- /// be left at its default value. In this case, however, it is
- /// important to note that callbacks installed on ONE_SHOT timers
- /// often reschedule the timer. If such callback is not executed
- /// the timer will have to be setup by the application when the
- /// thread is started again.
- ///
- /// Setting the @c run_pending_callbacks to true will guarantee
- /// that all callbacks for which the timers have already elapsed
- /// (and marked their watch sockets as ready) will be executed
- /// prior to the return from @c stopThread method. However, this
- /// should be avoided if the longer execution time of the
- /// @c stopThread function is a concern.
- ///
- /// This method has no effect if the thread is not running.
- ///
- /// @param run_pending_callbacks Indicates if the pending callbacks
- /// should be executed (if true).
- void stopThread(const bool run_pending_callbacks = false);
-
- //@}
-
- /// @brief Returns a reference to IO service used by the @c TimerMgr.
- asiolink::IOService& getIOService() const;
-
private:
/// @brief Private default constructor.
diff --git a/src/lib/dhcpsrv/writable_host_data_source.h b/src/lib/dhcpsrv/writable_host_data_source.h
index 2304a5370b..fcdef07910 100644
--- a/src/lib/dhcpsrv/writable_host_data_source.h
+++ b/src/lib/dhcpsrv/writable_host_data_source.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -47,7 +47,7 @@ public:
/// because a particular client may have reservations in multiple subnets.
///
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -71,7 +71,7 @@ public:
/// @brief Returns a host connected to the IPv4 subnet.
///
/// Implementations of this method should guard against the case when
- /// mutliple instances of the @c Host are present, e.g. when two
+ /// multiple instances of the @c Host are present, e.g. when two
/// @c Host objects are found, one for the DUID, another one for the
/// HW address. In such case, an implementation of this method
/// should throw an exception.
@@ -90,7 +90,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
@@ -103,7 +103,7 @@ public:
/// @brief Returns a host connected to the IPv6 subnet.
///
/// Implementations of this method should guard against the case when
- /// mutliple instances of the @c Host are present, e.g. when two
+ /// multiple instances of the @c Host are present, e.g. when two
/// @c Host objects are found, one for the DUID, another one for the
/// HW address. In such case, an implementation of this method
/// should throw an exception.
@@ -122,7 +122,7 @@ public:
///
/// @param subnet_id Subnet identifier.
/// @param identifier_type Identifier type.
- /// @param identifier_begin Pointer to a begining of a buffer containing
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
/// an identifier.
/// @param identifier_len Identifier length.
///
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index f21d2c3d04..7d197c9c89 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -101,7 +101,7 @@ BUILT_SOURCES += rdataclass.h rdataclass.cc
lib_LTLIBRARIES = libkea-dns++.la
-libkea_dns___la_LDFLAGS = -no-undefined -version-info 2:0:1
+libkea_dns___la_LDFLAGS = -no-undefined -version-info 1:1:1
libkea_dns___la_LDFLAGS += $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
libkea_dns___la_SOURCES =
diff --git a/src/lib/dns/labelsequence.cc b/src/lib/dns/labelsequence.cc
index c5682c3c7d..686efa65bf 100644
--- a/src/lib/dns/labelsequence.cc
+++ b/src/lib/dns/labelsequence.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -19,7 +19,7 @@ namespace dns {
LabelSequence::LabelSequence(const void* buf) {
#ifdef ENABLE_DEBUG
- // In non-debug mode, derefencing the NULL pointer further below
+ // In non-debug mode, dereferencing the NULL pointer further below
// will lead to a crash, so disabling this check is not
// unsafe. Except for a programming mistake, this case should not
// happen.
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 54bcc91a1b..a81ac0c892 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -1055,7 +1055,7 @@ MasterLoader::loadIncremental(size_t count_limit) {
}
bool
-MasterLoader::loadedSucessfully() const {
+MasterLoader::loadedSuccessfully() const {
return (impl_->complete_ && !impl_->seen_error_);
}
diff --git a/src/lib/dns/master_loader.h b/src/lib/dns/master_loader.h
index 45ac6f4fd7..42d45d1c99 100644
--- a/src/lib/dns/master_loader.h
+++ b/src/lib/dns/master_loader.h
@@ -132,7 +132,7 @@ public:
/// \note While this method works even before the loading is complete (by
/// returning false in that case), it is meant to be called only after
/// finishing the load.
- bool loadedSucessfully() const;
+ bool loadedSuccessfully() const;
/// \brief Return the total size of the zone files and streams.
///
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index 67c4ddd333..986bad0fa1 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -476,7 +476,7 @@ public:
const RRClass& rrclass, const RRType& rrtype) const;
/// \brief Determine whether the given section already has an RRset
- /// matching the one pointed to by the argumet
+ /// matching the one pointed to by the argument
///
/// \c section must be a valid constant of the \c Section type;
/// otherwise, an exception of class \c OutOfRange will be thrown.
diff --git a/src/lib/dns/messagerenderer.h b/src/lib/dns/messagerenderer.h
index 8fbaef8659..1b8b9c0f96 100644
--- a/src/lib/dns/messagerenderer.h
+++ b/src/lib/dns/messagerenderer.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -64,7 +64,7 @@ class LabelSequence;
/// implementation is really needed, we can make them virtual in future.
/// The only one that is virtual is writeName and it's because this
/// function is much more complicated, therefore there's a lot of space
-/// for different implementations or behaviours.
+/// for different implementations or different behavior.
class AbstractMessageRenderer {
public:
/// \brief Compression mode constants.
@@ -124,7 +124,7 @@ private:
///
/// It was decided that there's no need to have this in every subclass,
/// at least not now, and this reduces code size and gives compiler a
- /// better chance to optimise.
+ /// better chance to optimize.
isc::util::OutputBuffer* buffer_;
public:
///
@@ -390,6 +390,6 @@ private:
}
#endif // MESSAGERENDERER_H
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/dns/rdatafields.cc b/src/lib/dns/rdatafields.cc
index fd9992bf7c..bc41aba758 100644
--- a/src/lib/dns/rdatafields.cc
+++ b/src/lib/dns/rdatafields.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -98,7 +98,7 @@ public:
extendData();
return (fields_);
}
- // We use generict write* methods, with the exception of writeName.
+ // We use generic write* methods, with the exception of writeName.
// So new data can arrive without us knowing it, this considers all new
// data to be just data and extends the fields to take it into account.
size_t last_data_pos_;
diff --git a/src/lib/dns/rrclass-placeholder.h b/src/lib/dns/rrclass-placeholder.h
index fc201c6fa4..d4fd0baab0 100644
--- a/src/lib/dns/rrclass-placeholder.h
+++ b/src/lib/dns/rrclass-placeholder.h
@@ -111,7 +111,7 @@ public:
/// As for the format of "CLASSnnnn", "nnnn" must represent a valid 16-bit
/// unsigned integer, which may contain leading 0's as long as it consists
/// of at most 5 characters (inclusive).
- /// For example, "CLASS1" and "CLASSS001" are valid and represent the same
+ /// For example, "CLASS1" and "CLASS0001" are valid and represent the same
/// class, but "CLASS65536" and "CLASS000001" are invalid.
/// A "CLASSnnnn" representation is valid even if the corresponding class
/// code is registered in the \c RRParamRegistry object. For example, both
diff --git a/src/lib/dns/rrclass.h b/src/lib/dns/rrclass.h
index 04676a874f..1796b4388b 100644
--- a/src/lib/dns/rrclass.h
+++ b/src/lib/dns/rrclass.h
@@ -118,7 +118,7 @@ public:
/// As for the format of "CLASSnnnn", "nnnn" must represent a valid 16-bit
/// unsigned integer, which may contain leading 0's as long as it consists
/// of at most 5 characters (inclusive).
- /// For example, "CLASS1" and "CLASSS001" are valid and represent the same
+ /// For example, "CLASS1" and "CLASS0001" are valid and represent the same
/// class, but "CLASS65536" and "CLASS000001" are invalid.
/// A "CLASSnnnn" representation is valid even if the corresponding class
/// code is registered in the \c RRParamRegistry object. For example, both
diff --git a/src/lib/dns/rrcollator.h b/src/lib/dns/rrcollator.h
index cabe32060a..6ada41ed9e 100644
--- a/src/lib/dns/rrcollator.h
+++ b/src/lib/dns/rrcollator.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -18,7 +18,7 @@ namespace dns {
/// \brief A converter from a stream of RRs to a stream of collated RRsets
///
-/// This class is mainly intended to be a helper used as an adaptor for
+/// This class is mainly intended to be a helper used as an adapter for
/// user applications of the \c MasterLoader class; it works as a callback
/// for \c MasterLoader, buffers given RRs from the loader, collating
/// consecutive RRs that belong to the same RRset (ones having the same
@@ -93,7 +93,7 @@ public:
/// \brief Return \c MasterLoader compatible callback.
///
/// This method returns a functor in the form of \c AddRRCallback
- /// that works as an adaptor between \c MasterLoader and an application
+ /// that works as an adapter between \c MasterLoader and an application
/// that needs to get a stream of RRsets. When the returned callback
/// is called, this \c RRCollator object accepts the corresponding RR,
/// and collates it with other RRs of the same RRset if necessary.
diff --git a/src/lib/dns/rrset_collection_base.h b/src/lib/dns/rrset_collection_base.h
index a3d61243cb..ac77756e41 100644
--- a/src/lib/dns/rrset_collection_base.h
+++ b/src/lib/dns/rrset_collection_base.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -108,7 +108,7 @@ protected:
/// used by the public iterator. Derived classes of
/// \c RRsetCollectionBase are supposed to implement this class and
/// the \c getBeginning() and \c getEnd() methods, so that the
- /// public interator interface can be provided. This is a forward
+ /// public iterator interface can be provided. This is a forward
/// iterator only.
class Iter {
public:
diff --git a/src/lib/dns/serial.h b/src/lib/dns/serial.h
index b9dbcda2fb..7c7e3381b0 100644
--- a/src/lib/dns/serial.h
+++ b/src/lib/dns/serial.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -58,7 +58,7 @@ public:
/// \brief Direct assignment from value
///
- /// \param value the uint32_t value to assing
+ /// \param value the uint32_t value to assign
void operator=(uint32_t value) { value_ = value; }
/// \brief Returns the uint32_t representation of this serial value
diff --git a/src/lib/dns/tests/dns_exceptions_unittest.cc b/src/lib/dns/tests/dns_exceptions_unittest.cc
index bef41628e6..10ea1ef644 100644
--- a/src/lib/dns/tests/dns_exceptions_unittest.cc
+++ b/src/lib/dns/tests/dns_exceptions_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -42,6 +42,7 @@ TEST(DNSExceptionsTest, checkExceptionsHierarchy) {
const isc::dns::Exception& exception_cast2 =
dynamic_cast<const isc::dns::Exception&>(exception);
// to avoid compiler warning
+ exception_cast.getRcode();
exception_cast.what();
exception_cast2.what();
});
@@ -53,6 +54,7 @@ TEST(DNSExceptionsTest, checkExceptionsHierarchy) {
const isc::dns::Exception& exception_cast2 =
dynamic_cast<const isc::dns::Exception&>(exception);
// to avoid compiler warning
+ exception_cast.getRcode();
exception_cast.what();
exception_cast2.what();
});
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index 291f825e7f..6ef0147e0d 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -146,7 +146,7 @@ TEST_F(MasterLoaderTest, basicLoad) {
setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
RRClass::IN(), MasterLoader::MANY_ERRORS);
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
// The following three should be set to 0 initially in case the loader
// is constructed from a file name.
@@ -154,7 +154,7 @@ TEST_F(MasterLoaderTest, basicLoad) {
EXPECT_EQ(0, loader_->getPosition());
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_TRUE(warnings_.empty());
@@ -191,7 +191,7 @@ TEST_F(MasterLoaderTest, include) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_TRUE(warnings_.empty());
@@ -286,7 +286,7 @@ TEST_F(MasterLoaderTest, origin) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
// There's a relative origin in it, we warn about that.
EXPECT_EQ(1, warnings_.size());
@@ -326,7 +326,7 @@ TEST_F(MasterLoaderTest, generate) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
// The "before" and "after" scaffolding below checks that no
@@ -349,7 +349,7 @@ TEST_F(MasterLoaderTest, generateRelativeLHS) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
checkRR("example.org", RRType::NS(), "ns1.example.org.");
@@ -366,7 +366,7 @@ TEST_F(MasterLoaderTest, generateInFront) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
checkRR("9host.example.org", RRType::TXT(), "9 pomegranate");
@@ -383,7 +383,7 @@ TEST_F(MasterLoaderTest, generateInMiddle) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
checkRR("num9-host.example.org", RRType::TXT(), "This is 9 pomegranate");
@@ -400,7 +400,7 @@ TEST_F(MasterLoaderTest, generateAtEnd) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
checkRR("num9-host.example.org", RRType::TXT(), "Pomegranate9");
@@ -416,7 +416,7 @@ TEST_F(MasterLoaderTest, generateStripsQuotes) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
checkRR("example.org", RRType::MX(), "1 mx1.example.org.");
@@ -432,7 +432,7 @@ TEST_F(MasterLoaderTest, generateWithDoublePlaceholder) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
checkRR("host9.example.org", RRType::TXT(), "This is $ pomegranate");
@@ -448,7 +448,7 @@ TEST_F(MasterLoaderTest, generateWithEscape) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
checkRR("host9.example.org", RRType::TXT(), "This is \\$\\pomegranate");
@@ -469,7 +469,7 @@ TEST_F(MasterLoaderTest, generateWithParams) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
checkRR("host2.example.org", RRType::A(), "192.0.2.2");
@@ -499,7 +499,7 @@ TEST_F(MasterLoaderTest, generateWithStep) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
checkRR("host2.example.org", RRType::A(), "192.0.2.2");
@@ -558,7 +558,7 @@ TEST_F(MasterLoaderTest, generateWithModifiers) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
checkRR("host3.example.org", RRType::A(), "192.0.2.1");
@@ -609,7 +609,7 @@ TEST_F(MasterLoaderTest, generateWithNoModifiers) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
ASSERT_EQ(2, errors_.size()); // For the broken GENERATE
EXPECT_TRUE(warnings_.empty());
@@ -629,7 +629,7 @@ TEST_F(MasterLoaderTest, generateWithBadModifiers) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
ASSERT_EQ(2, errors_.size()); // For the broken GENERATE
EXPECT_TRUE(warnings_.empty());
@@ -648,7 +648,7 @@ TEST_F(MasterLoaderTest, generateMissingRange) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
EXPECT_TRUE(warnings_.empty());
@@ -665,7 +665,7 @@ TEST_F(MasterLoaderTest, generateMissingLHS) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
EXPECT_TRUE(warnings_.empty());
@@ -682,7 +682,7 @@ TEST_F(MasterLoaderTest, generateMissingType) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
EXPECT_TRUE(warnings_.empty());
@@ -699,7 +699,7 @@ TEST_F(MasterLoaderTest, generateMissingRHS) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
EXPECT_TRUE(warnings_.empty());
@@ -716,7 +716,7 @@ TEST_F(MasterLoaderTest, generateWithBadRangeSyntax) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
EXPECT_TRUE(warnings_.empty());
@@ -734,7 +734,7 @@ TEST_F(MasterLoaderTest, generateWithInvalidRange) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
EXPECT_TRUE(warnings_.empty());
@@ -751,7 +751,7 @@ TEST_F(MasterLoaderTest, generateWithInvalidClass) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
EXPECT_TRUE(warnings_.empty());
@@ -768,7 +768,7 @@ TEST_F(MasterLoaderTest, generateWithNoAvailableTTL) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size()); // For the broken GENERATE
EXPECT_TRUE(warnings_.empty());
@@ -787,7 +787,7 @@ TEST_F(MasterLoaderTest, popAfterError) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size()); // For the broken RR
EXPECT_EQ(1, warnings_.size()); // For missing EOLN
@@ -803,7 +803,7 @@ TEST_F(MasterLoaderTest, streamConstructor) {
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
// Unlike the basicLoad test, if we construct the loader from a stream
// getSize() returns the data size in the stream immediately after the
@@ -812,7 +812,7 @@ TEST_F(MasterLoaderTest, streamConstructor) {
EXPECT_EQ(0, loader_->getPosition());
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_TRUE(warnings_.empty());
@@ -831,9 +831,9 @@ TEST_F(MasterLoaderTest, incrementalLoad) {
setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
RRClass::IN(), MasterLoader::MANY_ERRORS);
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_FALSE(loader_->loadIncremental(2));
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_TRUE(warnings_.empty());
@@ -848,7 +848,7 @@ TEST_F(MasterLoaderTest, incrementalLoad) {
// Load the rest.
EXPECT_TRUE(loader_->loadIncremental(20));
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_TRUE(warnings_.empty());
@@ -861,7 +861,7 @@ TEST_F(MasterLoaderTest, incrementalLoad) {
// saying so.
TEST_F(MasterLoaderTest, invalidFile) {
setLoader("This file doesn't exist at all",
- Name("exmaple.org."), RRClass::IN(), MasterLoader::MANY_ERRORS);
+ Name("example.org."), RRClass::IN(), MasterLoader::MANY_ERRORS);
// Nothing yet. The loader is dormant until invoked.
// Is it really what we want?
@@ -941,7 +941,7 @@ struct ErrorCase {
{ "$INCLUDES " TEST_DATA_SRCDIR "/example.org",
"Unknown directive 'INCLUDES'", "Include too long" },
{ "$INCLUDE", "unexpected end of input", "Missing include path" },
- // The following two error messages are system dependant, omitting
+ // The following two error messages are system dependent, omitting
{ "$INCLUDE /file/not/found", NULL, "Include file not found" },
{ "$INCLUDE /file/not/found example.org. and here goes bunch of garbage",
NULL, "Include file not found and garbage at the end of line" },
@@ -977,9 +977,9 @@ TEST_F(MasterLoaderTest, brokenZone) {
stringstream zone_stream(zone);
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::DEFAULT);
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_THROW(loader_->load(), MasterLoaderError);
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size());
if (ec->reason != NULL) {
checkCallbackMessage(errors_.at(0), ec->reason, 2);
@@ -999,9 +999,9 @@ TEST_F(MasterLoaderTest, brokenZone) {
stringstream zone_stream(zone);
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_NO_THROW(loader_->load());
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size());
EXPECT_TRUE(warnings_.empty());
checkRR("example.org", RRType::SOA(), "ns1.example.org. "
@@ -1019,9 +1019,9 @@ TEST_F(MasterLoaderTest, brokenZone) {
stringstream zone_stream(zoneEOF);
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_NO_THROW(loader_->load());
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size()) << errors_[0] << "\n" << errors_[1];
// The unexpected EOF warning
EXPECT_EQ(1, warnings_.size());
@@ -1045,7 +1045,7 @@ TEST_F(MasterLoaderTest, includeWithGarbage) {
MasterLoader::MANY_ERRORS);
EXPECT_NO_THROW(loader_->load());
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
ASSERT_EQ(1, errors_.size());
checkCallbackMessage(errors_.at(0), "Extra tokens at the end of line", 1);
// It says something about extra tokens at the end
@@ -1064,7 +1064,7 @@ TEST_F(MasterLoaderTest, originWithGarbage) {
setLoader(ss, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
EXPECT_NO_THROW(loader_->load());
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
ASSERT_EQ(1, errors_.size());
checkCallbackMessage(errors_.at(0), "Extra tokens at the end of line", 1);
EXPECT_TRUE(warnings_.empty());
@@ -1086,7 +1086,7 @@ TEST_F(MasterLoaderTest, includeAndOrigin) {
MasterLoader::MANY_ERRORS);
// Successfully load the data
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_TRUE(warnings_.empty());
// And check it's the correct data
@@ -1106,7 +1106,7 @@ TEST_F(MasterLoaderTest, includeAndBadOrigin) {
setLoader(ss, Name("example.org"), RRClass::IN(),
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size());
checkCallbackMessage(errors_.at(0), "duplicate period in example..org.",
1);
@@ -1125,7 +1125,7 @@ TEST_F(MasterLoaderTest, includeOriginRestore) {
MasterLoader::MANY_ERRORS);
// Successfully load the data
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_TRUE(warnings_.empty());
// And check it's the correct data
@@ -1144,7 +1144,7 @@ TEST_F(MasterLoaderTest, includeAndInitialWS) {
MasterLoader::MANY_ERRORS);
// Successfully load the data
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_EQ(1, warnings_.size());
checkCallbackMessage(warnings_.at(0),
@@ -1173,7 +1173,7 @@ TEST_F(MasterLoaderTest, ttlDirective) {
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::DEFAULT);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
checkRR("example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
checkRR("a.example.org", RRType::A(), "192.0.2.2", RRTTL(100));
checkRR("b.example.org", RRType::A(), "192.0.2.3", RRTTL(3600));
@@ -1190,7 +1190,7 @@ TEST_F(MasterLoaderTest, ttlFromSOA) {
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::DEFAULT);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(1800));
checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
@@ -1209,7 +1209,7 @@ TEST_F(MasterLoaderTest, ttlFromPrevious) {
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::DEFAULT);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(1800));
@@ -1237,7 +1237,7 @@ TEST_F(MasterLoaderTest, RRParamsOrdering) {
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::DEFAULT);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(3600));
@@ -1257,7 +1257,7 @@ TEST_F(MasterLoaderTest, ttlFromPreviousSOA) {
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::DEFAULT);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(100));
checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(100));
@@ -1280,7 +1280,7 @@ TEST_F(MasterLoaderTest, ttlUnknownAndContinue) {
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
EXPECT_TRUE(warnings_.empty());
@@ -1295,7 +1295,7 @@ TEST_F(MasterLoaderTest, ttlUnknownAndEOF) {
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_TRUE(rrsets_.empty());
EXPECT_EQ(1, errors_.size());
@@ -1319,7 +1319,7 @@ TEST_F(MasterLoaderTest, ttlOverflow) {
MasterLoader::DEFAULT);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_EQ(3, rrsets_.size());
checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 2147483648", RRTTL(0));
@@ -1380,7 +1380,7 @@ TEST_F(MasterLoaderTest, noEOLN) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
// There should be one warning about the EOLN
EXPECT_EQ(1, warnings_.size());
@@ -1396,7 +1396,7 @@ TEST_F(MasterLoaderTest, noPreviousName) {
setLoader(ss, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadedSuccessfully());
EXPECT_EQ(1, errors_.size());
checkCallbackMessage(errors_.at(0), "No previous name to use in place of "
"initial whitespace", 1);
@@ -1411,7 +1411,7 @@ TEST_F(MasterLoaderTest, previousInInclude) {
setLoader(ss, Name("example.org"), RRClass::IN(),
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
// There should be one warning about the EOLN
EXPECT_EQ(1, warnings_.size());
@@ -1429,7 +1429,7 @@ TEST_F(MasterLoaderTest, numericOwnerName) {
MasterLoader::MANY_ERRORS);
loader_->load();
- EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(loader_->loadedSuccessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_TRUE(warnings_.empty());
diff --git a/src/lib/dns/tests/masterload_unittest.cc b/src/lib/dns/tests/masterload_unittest.cc
index 650400a850..a4c719d09c 100644
--- a/src/lib/dns/tests/masterload_unittest.cc
+++ b/src/lib/dns/tests/masterload_unittest.cc
@@ -390,7 +390,7 @@ TEST_F(MasterLoadTest, loadFromFile) {
EXPECT_THROW(masterLoad(NULL, origin, zclass, callback), MasterLoadError);
// Non existent file name. Ditto.
- EXPECT_THROW(masterLoad(TEST_DATA_BUILDDIR "/notexistent.txt", origin,
+ EXPECT_THROW(masterLoad(TEST_DATA_BUILDDIR "/nonexistent.txt", origin,
zclass, callback), MasterLoadError);
}
} // end namespace
diff --git a/src/lib/dns/tests/messagerenderer_unittest.cc b/src/lib/dns/tests/messagerenderer_unittest.cc
index 9296823b85..f5a85dd23a 100644
--- a/src/lib/dns/tests/messagerenderer_unittest.cc
+++ b/src/lib/dns/tests/messagerenderer_unittest.cc
@@ -46,7 +46,7 @@ protected:
const uint8_t MessageRendererTest::testdata[5] = {1, 2, 3, 4, 5};
// The test cases are borrowed from those for the OutputBuffer class.
-TEST_F(MessageRendererTest, writeIntger) {
+TEST_F(MessageRendererTest, writeInteger) {
renderer.writeUint16(data16);
expected_size += sizeof(data16);
diff --git a/src/lib/dns/tests/name_unittest.cc b/src/lib/dns/tests/name_unittest.cc
index c534877404..5f9813034b 100644
--- a/src/lib/dns/tests/name_unittest.cc
+++ b/src/lib/dns/tests/name_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -355,7 +355,7 @@ TEST_F(NameTest, atSign) {
EXPECT_EQ(Name::ROOT_NAME(), Name("@"));
// It is not alone. It is taken verbatim. We check the name converted
- // back to the textual form, since checking it agains other name object
+ // back to the textual form, since checking it against other name object
// may be wrong -- if we create it wrong the same way as the tested
// object.
EXPECT_EQ("\\@.", Name("@.").toText());
diff --git a/src/lib/dns/tests/rdata_nsec3_unittest.cc b/src/lib/dns/tests/rdata_nsec3_unittest.cc
index 296cb34639..457a980e4d 100644
--- a/src/lib/dns/tests/rdata_nsec3_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -204,7 +204,7 @@ TEST_F(Rdata_NSEC3_Test, compare) {
EXPECT_THROW(generic::NSEC3(nsec3_txt).compare(*rdata_nomatch),
bad_cast);
- // test RDATAs, sorted in the ascendent order. We only check comparison
+ // test RDATAs, sorted in the ascending order. We only check comparison
// on NSEC3-specific fields. Bitmap comparison is tested in the bitmap
// tests. Common cases for NSEC3 and NSECPARAM3 are in their shared tests.
vector<generic::NSEC3> compare_set;
diff --git a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
index fa490e735d..32cde683bf 100644
--- a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -244,7 +244,7 @@ TYPED_TEST(NSEC3PARAMLikeTest, toWire) {
}
TYPED_TEST(NSEC3PARAMLikeTest, compare) {
- // test RDATAs, sorted in the ascendent order.
+ // test RDATAs, sorted in the ascending order.
this->compare_set.push_back(this->fromText("0 0 0 D399EAAB" +
this->getCommonText()));
this->compare_set.push_back(this->fromText("1 0 0 D399EAAB" +
diff --git a/src/lib/dns/tests/rdata_nsec_unittest.cc b/src/lib/dns/tests/rdata_nsec_unittest.cc
index e59385476e..fb47b01c47 100644
--- a/src/lib/dns/tests/rdata_nsec_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -117,7 +117,7 @@ TEST_F(Rdata_NSEC_Test, compare) {
EXPECT_THROW(generic::NSEC(nsec_txt).compare(*rdata_nomatch),
bad_cast);
- // test RDATAs, sorted in the ascendent order. We only compare the
+ // test RDATAs, sorted in the ascending order. We only compare the
// next name here. Bitmap comparison is tested in the bitmap tests.
// Note that names are compared as wire-format data, not based on the
// domain name comparison.
diff --git a/src/lib/dns/tests/rdata_pimpl_holder_unittest.cc b/src/lib/dns/tests/rdata_pimpl_holder_unittest.cc
index e963b6c0e9..fd85fa0654 100644
--- a/src/lib/dns/tests/rdata_pimpl_holder_unittest.cc
+++ b/src/lib/dns/tests/rdata_pimpl_holder_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -20,7 +20,7 @@ TEST(RdataPimplHolderTest, all) {
EXPECT_EQ(i1, holder1.get());
// Obviously the value should match too.
EXPECT_EQ(42, *holder1.get());
- // We don't explictly delete i or holder1, so it should not leak
+ // We don't explicitly delete i or holder1, so it should not leak
// anything when the test is done (checked by Valgrind).
// The following cases are similar:
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index 442c0f51c5..bca07fd28d 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -231,7 +231,7 @@ TEST_F(Rdata_SOA_Test, compare) {
// Compare other numeric fields: 1076895760 = 0x40302010,
// 270544960 = 0x10203040. These are chosen to make sure that machine
- // endian doesn't confuse the comparison results.
+ // endianness doesn't confuse the comparison results.
compareCheck(generic::SOA(". . 270544960 0 0 0 0"),
generic::SOA(". . 1076895760 0 0 0 0"));
compareCheck(generic::SOA(". . 0 270544960 0 0 0"),
diff --git a/src/lib/dns/tests/rdata_srv_unittest.cc b/src/lib/dns/tests/rdata_srv_unittest.cc
index 1f5b669de6..3b0d734e9a 100644
--- a/src/lib/dns/tests/rdata_srv_unittest.cc
+++ b/src/lib/dns/tests/rdata_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -179,7 +179,7 @@ TEST_F(Rdata_SRV_Test, toText) {
}
TEST_F(Rdata_SRV_Test, compare) {
- // test RDATAs, sorted in the ascendent order.
+ // test RDATAs, sorted in the ascending order.
vector<in::SRV> compare_set;
compare_set.push_back(in::SRV("1 5 1500 a.example.com."));
compare_set.push_back(in::SRV("2 5 1500 a.example.com."));
diff --git a/src/lib/dns/tests/rdata_tsig_unittest.cc b/src/lib/dns/tests/rdata_tsig_unittest.cc
index cbfe7be218..fc83220ad0 100644
--- a/src/lib/dns/tests/rdata_tsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_tsig_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -167,7 +167,7 @@ TEST_F(Rdata_TSIG_Test, badText) {
checkFromText_LexerError("foo 0 FUDGE 0 0 BADKEY 0");
// MAC size is too large
checkFromText_InvalidText("foo 0 0 65536 0 BADKEY 0");
- // invalide MAC size (negative)
+ // invalid MAC size (negative)
checkFromText_LexerError("foo 0 0 -1 0 BADKEY 0");
// invalid MAC size (not a number)
checkFromText_LexerError("foo 0 0 MACSIZE 0 BADKEY 0");
@@ -288,7 +288,7 @@ TEST_F(Rdata_TSIG_Test, createFromParams) {
17, 0, NULL)));
const uint8_t fake_data[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84,
- 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
+ 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
EXPECT_EQ(0, any::TSIG(valid_text2).compare(
any::TSIG(Name("hmac-sha256"), 1286779327, 300, 12,
fake_data, 16020, 16, 0, NULL)));
@@ -399,7 +399,7 @@ TEST_F(Rdata_TSIG_Test, toText) {
}
TEST_F(Rdata_TSIG_Test, compare) {
- // test RDATAs, sorted in the ascendent order.
+ // test RDATAs, sorted in the ascending order.
// "AAAA" encoded in BASE64 corresponds to 0x000000, so it should be the
// smallest data of the same length.
vector<any::TSIG> compare_set;
diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc
index 7261b72008..0bcfeeb0e2 100644
--- a/src/lib/dns/tests/rrclass_unittest.cc
+++ b/src/lib/dns/tests/rrclass_unittest.cc
@@ -41,7 +41,7 @@ const RRClass RRClassTest::rrclass_0x80(0x80);
const RRClass RRClassTest::rrclass_0x800(0x800);
const RRClass RRClassTest::rrclass_0x8000(0x8000);
const RRClass RRClassTest::rrclass_max(0xffff);
-// This is wire-format data for the above sample RRClasss rendered in the
+// This is wire-format data for the above sample RRClass rendered in the
// appearing order.
const uint8_t RRClassTest::wiredata[] = { 0x00, 0x01, 0x00, 0x80, 0x08,
0x00, 0x80, 0x00, 0xff, 0xff };
@@ -124,7 +124,7 @@ TEST_F(RRClassTest, toWireRenderer) {
renderer.getData(), renderer.getLength());
}
-TEST_F(RRClassTest, wellKnownClasss) {
+TEST_F(RRClassTest, wellKnownClass) {
EXPECT_EQ(1, RRClass::IN().getCode());
EXPECT_EQ("IN", RRClass::IN().toText());
}
diff --git a/src/lib/dns/tests/testdata/message_toWire5.spec b/src/lib/dns/tests/testdata/message_toWire5.spec
index e97fb43ce0..e31683380a 100644
--- a/src/lib/dns/tests/testdata/message_toWire5.spec
+++ b/src/lib/dns/tests/testdata/message_toWire5.spec
@@ -1,5 +1,5 @@
#
-# A longest possible (without EDNS) DNS response with TSIG, i.e. totatl
+# A longest possible (without EDNS) DNS response with TSIG, i.e. total
# length should be 512 bytes.
#
diff --git a/src/lib/dns/tests/testdata/rdata_cname_fromWire b/src/lib/dns/tests/testdata/rdata_cname_fromWire
index 79cfc5b1b8..57eae13b62 100644
--- a/src/lib/dns/tests/testdata/rdata_cname_fromWire
+++ b/src/lib/dns/tests/testdata/rdata_cname_fromWire
@@ -2,7 +2,7 @@
# various kinds of CNAME RDATA stored in an input buffer
#
# Valid non-compressed RDATA for cn.example.com.
-# RDLENGHT=16 bytes
+# RDLENGTH=16 bytes
# 0 1
00 10
# 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7(bytes)
diff --git a/src/lib/dns/tests/testdata/rdata_dhcid_fromWire b/src/lib/dns/tests/testdata/rdata_dhcid_fromWire
index 0c8d56a53c..b28b5b3ace 100644
--- a/src/lib/dns/tests/testdata/rdata_dhcid_fromWire
+++ b/src/lib/dns/tests/testdata/rdata_dhcid_fromWire
@@ -3,7 +3,7 @@
#
# Valid RDATA for 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
#
-# RDLENGHT=41 bytes
+# RDLENGTH=41 bytes
# 0 1
00 29
# 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
diff --git a/src/lib/dns/tests/testdata/rdata_dname_fromWire b/src/lib/dns/tests/testdata/rdata_dname_fromWire
index bd41236164..1c899bf19d 100644
--- a/src/lib/dns/tests/testdata/rdata_dname_fromWire
+++ b/src/lib/dns/tests/testdata/rdata_dname_fromWire
@@ -2,7 +2,7 @@
# various kinds of DNAME RDATA stored in an input buffer
#
# Valid non-compressed RDATA for dn.example.com.
-# RDLENGHT=16 bytes
+# RDLENGTH=16 bytes
# 0 1
00 10
# 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7(bytes)
diff --git a/src/lib/dns/tests/testdata/rdata_mx_fromWire b/src/lib/dns/tests/testdata/rdata_mx_fromWire
index 407350f1dd..8e09154e53 100644
--- a/src/lib/dns/tests/testdata/rdata_mx_fromWire
+++ b/src/lib/dns/tests/testdata/rdata_mx_fromWire
@@ -3,7 +3,7 @@
#
# Valid RDATA for "10 mail.example.com"
#
-# RDLENGHT=18 bytes
+# RDLENGTH=18 bytes
# 0 1
00 12
# 2 3
diff --git a/src/lib/dns/tests/testdata/rdata_ns_fromWire b/src/lib/dns/tests/testdata/rdata_ns_fromWire
index dd0f599d45..973365f059 100644
--- a/src/lib/dns/tests/testdata/rdata_ns_fromWire
+++ b/src/lib/dns/tests/testdata/rdata_ns_fromWire
@@ -2,7 +2,7 @@
# various kinds of NS RDATA stored in an input buffer
#
# Valid non-compressed RDATA for ns.example.com.
-# RDLENGHT=16 bytes
+# RDLENGTH=16 bytes
# 0 1
00 10
# 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7(bytes)
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
index 0b6a5afeac..f35de8399f 100644
--- a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
@@ -1,6 +1,6 @@
#
# A malformed NSEC3 RDATA: RDLEN indicates it doesn't even contain the fixed
-# 5 octects
+# 5 octets
#
[custom]
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec
index bce65ff138..e04b818e98 100644
--- a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec
@@ -1,6 +1,6 @@
#
# A malformed NSEC3PARAM RDATA: RDLEN indicates it doesn't even contain the
-# fixed 5 octects
+# fixed 5 octets
#
[custom]
diff --git a/src/lib/dns/tests/testdata/rdata_soa_fromWire b/src/lib/dns/tests/testdata/rdata_soa_fromWire
index 0fa65d66ba..5cd67f053b 100644
--- a/src/lib/dns/tests/testdata/rdata_soa_fromWire
+++ b/src/lib/dns/tests/testdata/rdata_soa_fromWire
@@ -3,7 +3,7 @@
#
# Valid compressed RDATA for "(ns.example.com. root.example.com.
# 2010012601 3600 300 3600000 1200)"
-# RDLENGHT=43 bytes
+# RDLENGTH=43 bytes
# 0 1
00 2b
# MNAME: non compressed
diff --git a/src/lib/dns/tests/testdata/rdata_srv_fromWire b/src/lib/dns/tests/testdata/rdata_srv_fromWire
index dac87e9144..0f1e4ec75a 100644
--- a/src/lib/dns/tests/testdata/rdata_srv_fromWire
+++ b/src/lib/dns/tests/testdata/rdata_srv_fromWire
@@ -1,7 +1,7 @@
#
# various kinds of SRV RDATA stored in an input buffer
#
-# RDLENGHT=21 bytes
+# RDLENGTH=21 bytes
# 0 1
00 15
# 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 20 1 2(bytes)
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire1 b/src/lib/dns/tests/testdata/rdata_txt_fromWire1
index 95980dbfc9..547d76f77d 100644
--- a/src/lib/dns/tests/testdata/rdata_txt_fromWire1
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire1
@@ -3,7 +3,7 @@
#
# Valid RDATA for "Test-String"
#
-# RDLENGHT=12 bytes
+# RDLENGTH=12 bytes
00 0c
# T e s t - S t r i n g
0b 54 65 73 74 2d 53 74 72 69 6e 67
diff --git a/src/lib/dns/tests/unittest_util.h b/src/lib/dns/tests/unittest_util.h
index b0594bb385..65b60cea76 100644
--- a/src/lib/dns/tests/unittest_util.h
+++ b/src/lib/dns/tests/unittest_util.h
@@ -42,7 +42,7 @@ public:
///
/// This check method uses \c Name::compare() for comparison, which performs
/// deeper checks including the equality of offsets, and should be better
- /// than EXPECT_EQ, which uses operater==. Like the \c matchWireData()
+ /// than EXPECT_EQ, which uses operator==. Like the \c matchWireData()
/// method, the usage is a bit awkward; the caller should use
/// \c EXPECT_PRED_FORMAT2.
///
diff --git a/src/lib/dns/tests/zone_checker_unittest.cc b/src/lib/dns/tests/zone_checker_unittest.cc
index 9530d7a0fa..1013f6ce32 100644
--- a/src/lib/dns/tests/zone_checker_unittest.cc
+++ b/src/lib/dns/tests/zone_checker_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -130,7 +130,7 @@ TEST_F(ZoneCheckerTest, checkSOA) {
// If null callback is specified, checkZone() only returns the final
// result.
ZoneCheckerCallbacks noerror_callbacks(
- NULL, boost::bind(&ZoneCheckerTest::callback, this, _1, false));
+ 0, boost::bind(&ZoneCheckerTest::callback, this, _1, false));
EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, noerror_callbacks));
checkIssues();
@@ -194,7 +194,7 @@ TEST_F(ZoneCheckerTest, checkNSData) {
// Same check, but disabling warning callback. Same result, but without
// the warning.
ZoneCheckerCallbacks nowarn_callbacks(
- boost::bind(&ZoneCheckerTest::callback, this, _1, true), NULL);
+ boost::bind(&ZoneCheckerTest::callback, this, _1, true), 0);
EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, nowarn_callbacks));
checkIssues();
diff --git a/src/lib/dns/tsig.h b/src/lib/dns/tsig.h
index 046e46e31c..579f8e4863 100644
--- a/src/lib/dns/tsig.h
+++ b/src/lib/dns/tsig.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -414,7 +414,7 @@ public:
protected:
/// \brief Update internal HMAC state by more data.
///
- /// This is used mostly internaly, when we need to verify a message without
+ /// This is used mostly internally, when we need to verify a message without
/// TSIG signature in the middle of signed TCP stream. However, it is also
/// used in tests, so it's protected instead of private, to allow tests
/// in.
diff --git a/src/lib/eval/Makefile.am b/src/lib/eval/Makefile.am
index c879708496..fd6eca48d1 100644
--- a/src/lib/eval/Makefile.am
+++ b/src/lib/eval/Makefile.am
@@ -36,7 +36,7 @@ libkea_eval_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
libkea_eval_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
libkea_eval_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
-libkea_eval_la_LDFLAGS = -no-undefined -version-info 4:0:0
+libkea_eval_la_LDFLAGS = -no-undefined -version-info 5:0:0
libkea_eval_la_LDFLAGS += $(CRYPTO_LDFLAGS)
EXTRA_DIST = eval.dox
diff --git a/src/lib/eval/eval.dox b/src/lib/eval/eval.dox
index 9f27ad5ad6..8c5fa1b157 100644
--- a/src/lib/eval/eval.dox
+++ b/src/lib/eval/eval.dox
@@ -1,11 +1,11 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/**
- @page dhcpEval libeval - Expression evaluation and client classification
+ @page libeval libkea-eval - Expression Evaluation and Client Classification Library
@section dhcpEvalIntroduction Introduction
diff --git a/src/lib/eval/eval_context.cc b/src/lib/eval/eval_context.cc
index d90d71bee5..322e2e25e6 100644
--- a/src/lib/eval/eval_context.cc
+++ b/src/lib/eval/eval_context.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -27,11 +27,11 @@ EvalContext::~EvalContext()
}
bool
-EvalContext::parseString(const std::string& str)
+EvalContext::parseString(const std::string& str, ParserType type)
{
file_ = "<string>";
string_ = str;
- scanStringBegin();
+ scanStringBegin(type);
isc::eval::EvalParser parser(*this);
parser.set_debug_level(trace_parsing_);
int res = parser.parse();
@@ -96,15 +96,15 @@ EvalContext::convertOptionName(const std::string& option_name,
return (option_def->getCode());
}
-uint8_t
+int8_t
EvalContext::convertNestLevelNumber(const std::string& nest_level,
const isc::eval::location& loc)
{
- uint8_t n = convertUint8(nest_level, loc);
+ int8_t n = convertInt8(nest_level, loc);
if (option_universe_ == Option::V6) {
- if (n >= HOP_COUNT_LIMIT) {
+ if ((n < - HOP_COUNT_LIMIT) || (n >= HOP_COUNT_LIMIT)) {
error(loc, "Nest level has invalid value in "
- + nest_level + ". Allowed range: 0..31");
+ + nest_level + ". Allowed range: -32..31");
}
} else {
error(loc, "Nest level invalid for DHCPv4 packets");
@@ -123,7 +123,26 @@ EvalContext::convertUint8(const std::string& number,
} catch (const boost::bad_lexical_cast &) {
error(loc, "Invalid integer value in " + number);
}
- if (n < 0 || n >= std::numeric_limits<uint8_t>::max()) {
+ if (n < 0 || n > std::numeric_limits<uint8_t>::max()) {
+ error(loc, "Invalid value in "
+ + number + ". Allowed range: 0..255");
+ }
+
+ return (static_cast<uint8_t>(n));
+}
+
+int8_t
+EvalContext::convertInt8(const std::string& number,
+ const isc::eval::location& loc)
+{
+ int n = 0;
+ try {
+ n = boost::lexical_cast<int>(number);
+ } catch (const boost::bad_lexical_cast &) {
+ error(loc, "Invalid integer value in " + number);
+ }
+ if (n < std::numeric_limits<int8_t>::min() ||
+ n > std::numeric_limits<int8_t>::max()) {
error(loc, "Invalid value in "
+ number + ". Allowed range: 0..255");
}
diff --git a/src/lib/eval/eval_context.h b/src/lib/eval/eval_context.h
index e0a938bfaf..96c683604c 100644
--- a/src/lib/eval/eval_context.h
+++ b/src/lib/eval/eval_context.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -22,7 +22,7 @@ YY_DECL;
namespace isc {
namespace eval {
-/// @brief Evaluation error exception raised when trying to parse an axceptions.
+/// @brief Evaluation error exception raised when trying to parse an exceptions.
class EvalParseError : public isc::Exception {
public:
EvalParseError(const char* file, size_t line, const char* what) :
@@ -34,6 +34,14 @@ public:
class EvalContext
{
public:
+
+ /// @brief Specifies what type of expression the parser is expected to see
+ typedef enum {
+ PARSER_BOOL, ///< expression is expected to evaluate to bool
+ PARSER_STRING ///< expression is expected to evaluate to string
+ } ParserType;
+
+
/// @brief Default constructor.
///
/// @param option_universe Option universe: DHCPv4 or DHCPv6. This is used
@@ -48,16 +56,19 @@ public:
isc::dhcp::Expression expression;
/// @brief Method called before scanning starts on a string.
- void scanStringBegin();
+ ///
+ /// @param type specifies type of the expression to be parsed
+ void scanStringBegin(ParserType type);
/// @brief Method called after the last tokens are scanned from a string.
void scanStringEnd();
/// @brief Run the parser on the string specified.
///
- /// @param str string to be written
+ /// @param str string to be parsed
+ /// @param type type of the expression expected/parser type to be created
/// @return true on success.
- bool parseString(const std::string& str);
+ bool parseString(const std::string& str, ParserType type = PARSER_BOOL);
/// @brief The name of the file being parsed.
/// Used later to pass the file name to the location tracker.
@@ -122,15 +133,24 @@ public:
static uint8_t convertUint8(const std::string& number,
const isc::eval::location& loc);
+ /// @brief Attempts to convert string to signed 8bit integer
+ ///
+ /// @param number string to be converted
+ /// @param loc the location of the token
+ /// @return the integer value
+ /// @throw EvalParseError if conversion fails or the value is out of range.
+ static int8_t convertInt8(const std::string& number,
+ const isc::eval::location& loc);
+
/// @brief Nest level conversion
///
/// @param nest_level a string representing the integer nesting level
/// @param loc the location of the token
/// @return the nesting level
/// @throw calls the syntax error function if the value is not in
- /// the range 0..31
- uint8_t convertNestLevelNumber(const std::string& nest_level,
- const isc::eval::location& loc);
+ /// the range -32..31
+ int8_t convertNestLevelNumber(const std::string& nest_level,
+ const isc::eval::location& loc);
/// @brief Converts integer to string representation
///
@@ -153,7 +173,7 @@ private:
/// @brief Flag determining scanner debugging.
bool trace_scanning_;
- /// @brief Flag determing parser debugging.
+ /// @brief Flag determining parser debugging.
bool trace_parsing_;
/// @brief Option universe: DHCPv4 or DHCPv6.
diff --git a/src/lib/eval/eval_log.h b/src/lib/eval/eval_log.h
index c7fa5001fd..0b33c43717 100644
--- a/src/lib/eval/eval_log.h
+++ b/src/lib/eval/eval_log.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -19,11 +19,11 @@ namespace dhcp {
/// Note that higher numbers equate to more verbose (and detailed) output.
// The first level traces normal operations,
-const int EVAL_DBG_TRACE = DBGLVL_TRACE_BASIC;
+const int EVAL_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
// Additional information on the calls. Report the values that were
// popped from or pushed to the value stack.
-const int EVAL_DBG_STACK = DBGLVL_TRACE_DETAIL_DATA;
+const int EVAL_DBG_STACK = isc::log::DBGLVL_TRACE_DETAIL_DATA;
/// @brief Eval Logger
///
diff --git a/src/lib/eval/eval_messages.mes b/src/lib/eval/eval_messages.mes
index 48b0052838..9229536922 100644
--- a/src/lib/eval/eval_messages.mes
+++ b/src/lib/eval/eval_messages.mes
@@ -48,7 +48,7 @@ This debug message indicates that the first value is popped from
the value stack, negated and then pushed onto the value stack.
The string is displayed in text.
-# For use with TokenOption based classes. These include TokenOpton,
+# For use with TokenOption based classes. These include TokenOption,
# TokenRelay4Option and TokenRelay6Option.
% EVAL_DEBUG_OPTION Pushing option %1 with value %2
diff --git a/src/lib/eval/evaluate.cc b/src/lib/eval/evaluate.cc
index bb947f1991..dbc39c54cf 100644
--- a/src/lib/eval/evaluate.cc
+++ b/src/lib/eval/evaluate.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,7 +9,7 @@
namespace isc {
namespace dhcp {
-bool evaluate(const Expression& expr, Pkt& pkt) {
+bool evaluateBool(const Expression& expr, Pkt& pkt) {
ValueStack values;
for (Expression::const_iterator it = expr.begin();
it != expr.end(); ++it) {
@@ -17,10 +17,24 @@ bool evaluate(const Expression& expr, Pkt& pkt) {
}
if (values.size() != 1) {
isc_throw(EvalBadStack, "Incorrect stack order. Expected exactly "
- "1 value at the end of evaluatuion, got " << values.size());
+ "1 value at the end of evaluation, got " << values.size());
}
return (Token::toBool(values.top()));
}
+std::string
+evaluateString(const Expression& expr, Pkt& pkt) {
+ ValueStack values;
+ for (auto it = expr.begin(); it != expr.end(); ++it) {
+ (*it)->evaluate(pkt, values);
+ }
+ if (values.size() != 1) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected exactly "
+ "1 value at the end of evaluation, got " << values.size());
+ }
+ return (values.top());
+}
+
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/lib/eval/evaluate.h b/src/lib/eval/evaluate.h
index 2ab018cbe8..b2f6eb23e1 100644
--- a/src/lib/eval/evaluate.h
+++ b/src/lib/eval/evaluate.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#define EVALUATE_H
#include <eval/token.h>
+#include <string>
namespace isc {
namespace dhcp {
@@ -22,7 +23,10 @@ namespace dhcp {
/// stack at the end of the evaluation
/// @throw EvalTypeError if the value at the top of the stack at the
/// end of the evaluation is not "false" or "true"
-bool evaluate(const Expression& expr, Pkt& pkt);
+bool evaluateBool(const Expression& expr, Pkt& pkt);
+
+
+std::string evaluateString(const Expression& expr, Pkt& pkt);
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/lib/eval/lexer.cc b/src/lib/eval/lexer.cc
index 9984c9f716..a04aed10dc 100644
--- a/src/lib/eval/lexer.cc
+++ b/src/lib/eval/lexer.cc
@@ -1,22 +1,27 @@
-#line 2 "lexer.cc"
+#line 1 "lexer.cc"
-#line 4 "lexer.cc"
+#line 3 "lexer.cc"
#define YY_INT_ALIGNED short int
/* A lexical scanner generated by flex */
/* %not-for-header */
-
/* %if-c-only */
/* %if-not-reentrant */
#define yy_create_buffer eval_create_buffer
#define yy_delete_buffer eval_delete_buffer
-#define yy_flex_debug eval_flex_debug
+#define yy_scan_buffer eval_scan_buffer
+#define yy_scan_string eval_scan_string
+#define yy_scan_bytes eval_scan_bytes
#define yy_init_buffer eval_init_buffer
#define yy_flush_buffer eval_flush_buffer
#define yy_load_buffer_state eval_load_buffer_state
#define yy_switch_to_buffer eval_switch_to_buffer
+#define yypush_buffer_state evalpush_buffer_state
+#define yypop_buffer_state evalpop_buffer_state
+#define yyensure_buffer_stack evalensure_buffer_stack
+#define yy_flex_debug eval_flex_debug
#define yyin evalin
#define yyleng evalleng
#define yylex evallex
@@ -36,7 +41,7 @@
#define FLEX_SCANNER
#define YY_FLEX_MAJOR_VERSION 2
#define YY_FLEX_MINOR_VERSION 6
-#define YY_FLEX_SUBMINOR_VERSION 1
+#define YY_FLEX_SUBMINOR_VERSION 4
#if YY_FLEX_SUBMINOR_VERSION > 0
#define FLEX_BETA
#endif
@@ -45,11 +50,244 @@
/* %endif */
/* %if-c-only */
-
+#ifdef yy_create_buffer
+#define eval_create_buffer_ALREADY_DEFINED
+#else
+#define yy_create_buffer eval_create_buffer
+#endif
+
+#ifdef yy_delete_buffer
+#define eval_delete_buffer_ALREADY_DEFINED
+#else
+#define yy_delete_buffer eval_delete_buffer
+#endif
+
+#ifdef yy_scan_buffer
+#define eval_scan_buffer_ALREADY_DEFINED
+#else
+#define yy_scan_buffer eval_scan_buffer
+#endif
+
+#ifdef yy_scan_string
+#define eval_scan_string_ALREADY_DEFINED
+#else
+#define yy_scan_string eval_scan_string
+#endif
+
+#ifdef yy_scan_bytes
+#define eval_scan_bytes_ALREADY_DEFINED
+#else
+#define yy_scan_bytes eval_scan_bytes
+#endif
+
+#ifdef yy_init_buffer
+#define eval_init_buffer_ALREADY_DEFINED
+#else
+#define yy_init_buffer eval_init_buffer
+#endif
+
+#ifdef yy_flush_buffer
+#define eval_flush_buffer_ALREADY_DEFINED
+#else
+#define yy_flush_buffer eval_flush_buffer
+#endif
+
+#ifdef yy_load_buffer_state
+#define eval_load_buffer_state_ALREADY_DEFINED
+#else
+#define yy_load_buffer_state eval_load_buffer_state
+#endif
+
+#ifdef yy_switch_to_buffer
+#define eval_switch_to_buffer_ALREADY_DEFINED
+#else
+#define yy_switch_to_buffer eval_switch_to_buffer
+#endif
+
+#ifdef yypush_buffer_state
+#define evalpush_buffer_state_ALREADY_DEFINED
+#else
+#define yypush_buffer_state evalpush_buffer_state
+#endif
+
+#ifdef yypop_buffer_state
+#define evalpop_buffer_state_ALREADY_DEFINED
+#else
+#define yypop_buffer_state evalpop_buffer_state
+#endif
+
+#ifdef yyensure_buffer_stack
+#define evalensure_buffer_stack_ALREADY_DEFINED
+#else
+#define yyensure_buffer_stack evalensure_buffer_stack
+#endif
+
+#ifdef yylex
+#define evallex_ALREADY_DEFINED
+#else
+#define yylex evallex
+#endif
+
+#ifdef yyrestart
+#define evalrestart_ALREADY_DEFINED
+#else
+#define yyrestart evalrestart
+#endif
+
+#ifdef yylex_init
+#define evallex_init_ALREADY_DEFINED
+#else
+#define yylex_init evallex_init
+#endif
+
+#ifdef yylex_init_extra
+#define evallex_init_extra_ALREADY_DEFINED
+#else
+#define yylex_init_extra evallex_init_extra
+#endif
+
+#ifdef yylex_destroy
+#define evallex_destroy_ALREADY_DEFINED
+#else
+#define yylex_destroy evallex_destroy
+#endif
+
+#ifdef yyget_debug
+#define evalget_debug_ALREADY_DEFINED
+#else
+#define yyget_debug evalget_debug
+#endif
+
+#ifdef yyset_debug
+#define evalset_debug_ALREADY_DEFINED
+#else
+#define yyset_debug evalset_debug
+#endif
+
+#ifdef yyget_extra
+#define evalget_extra_ALREADY_DEFINED
+#else
+#define yyget_extra evalget_extra
+#endif
+
+#ifdef yyset_extra
+#define evalset_extra_ALREADY_DEFINED
+#else
+#define yyset_extra evalset_extra
+#endif
+
+#ifdef yyget_in
+#define evalget_in_ALREADY_DEFINED
+#else
+#define yyget_in evalget_in
+#endif
+
+#ifdef yyset_in
+#define evalset_in_ALREADY_DEFINED
+#else
+#define yyset_in evalset_in
+#endif
+
+#ifdef yyget_out
+#define evalget_out_ALREADY_DEFINED
+#else
+#define yyget_out evalget_out
+#endif
+
+#ifdef yyset_out
+#define evalset_out_ALREADY_DEFINED
+#else
+#define yyset_out evalset_out
+#endif
+
+#ifdef yyget_leng
+#define evalget_leng_ALREADY_DEFINED
+#else
+#define yyget_leng evalget_leng
+#endif
+
+#ifdef yyget_text
+#define evalget_text_ALREADY_DEFINED
+#else
+#define yyget_text evalget_text
+#endif
+
+#ifdef yyget_lineno
+#define evalget_lineno_ALREADY_DEFINED
+#else
+#define yyget_lineno evalget_lineno
+#endif
+
+#ifdef yyset_lineno
+#define evalset_lineno_ALREADY_DEFINED
+#else
+#define yyset_lineno evalset_lineno
+#endif
+
+#ifdef yywrap
+#define evalwrap_ALREADY_DEFINED
+#else
+#define yywrap evalwrap
+#endif
+
/* %endif */
+#ifdef yyalloc
+#define evalalloc_ALREADY_DEFINED
+#else
+#define yyalloc evalalloc
+#endif
+
+#ifdef yyrealloc
+#define evalrealloc_ALREADY_DEFINED
+#else
+#define yyrealloc evalrealloc
+#endif
+
+#ifdef yyfree
+#define evalfree_ALREADY_DEFINED
+#else
+#define yyfree evalfree
+#endif
+
/* %if-c-only */
+#ifdef yytext
+#define evaltext_ALREADY_DEFINED
+#else
+#define yytext evaltext
+#endif
+
+#ifdef yyleng
+#define evalleng_ALREADY_DEFINED
+#else
+#define yyleng evalleng
+#endif
+
+#ifdef yyin
+#define evalin_ALREADY_DEFINED
+#else
+#define yyin evalin
+#endif
+
+#ifdef yyout
+#define evalout_ALREADY_DEFINED
+#else
+#define yyout evalout
+#endif
+
+#ifdef yy_flex_debug
+#define eval_flex_debug_ALREADY_DEFINED
+#else
+#define yy_flex_debug eval_flex_debug
+#endif
+
+#ifdef yylineno
+#define evallineno_ALREADY_DEFINED
+#else
+#define yylineno evallineno
+#endif
+
/* %endif */
/* First, we deal with platform-specific or compiler-specific issues. */
@@ -127,12 +365,17 @@ typedef unsigned int flex_uint32_t;
#define UINT32_MAX (4294967295U)
#endif
+#ifndef SIZE_MAX
+#define SIZE_MAX (~(size_t)0)
+#endif
+
#endif /* ! C99 */
#endif /* ! FLEXINT_H */
/* %endif */
+/* begin standard C++ headers. */
/* %if-c++-only */
/* %endif */
@@ -146,19 +389,15 @@ typedef unsigned int flex_uint32_t;
#endif
/* %not-for-header */
-
/* Returned upon end-of-file. */
#define YY_NULL 0
/* %ok-for-header */
/* %not-for-header */
-
-/* Promotes a possibly negative, possibly signed char to an unsigned
- * integer for use as an array index. If the signed char is negative,
- * we want to instead treat it as an 8-bit unsigned char, hence the
- * double cast.
+/* Promotes a possibly negative, possibly signed char to an
+ * integer in range [0..255] for use as an array index.
*/
-#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+#define YY_SC_TO_UI(c) ((YY_CHAR) (c))
/* %ok-for-header */
/* %if-reentrant */
@@ -173,20 +412,16 @@ typedef unsigned int flex_uint32_t;
* definition of BEGIN.
*/
#define BEGIN (yy_start) = 1 + 2 *
-
/* Translate the current start state into a value that can be later handed
* to BEGIN to return to the state. The YYSTATE alias is for lex
* compatibility.
*/
#define YY_START (((yy_start) - 1) / 2)
#define YYSTATE YY_START
-
/* Action number for EOF rule of a given start state. */
#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
-
/* Special action meaning "start processing a new file". */
-#define YY_NEW_FILE evalrestart(evalin )
-
+#define YY_NEW_FILE yyrestart( yyin )
#define YY_END_OF_BUFFER_CHAR 0
/* Size of default input buffer. */
@@ -217,55 +452,54 @@ typedef size_t yy_size_t;
#endif
/* %if-not-reentrant */
-extern int evalleng;
+extern int yyleng;
/* %endif */
/* %if-c-only */
/* %if-not-reentrant */
-extern FILE *evalin, *evalout;
+extern FILE *yyin, *yyout;
/* %endif */
/* %endif */
#define EOB_ACT_CONTINUE_SCAN 0
#define EOB_ACT_END_OF_FILE 1
#define EOB_ACT_LAST_MATCH 2
-
+
/* Note: We specifically omit the test for yy_rule_can_match_eol because it requires
* access to the local variable yy_act. Since yyless() is a macro, it would break
- * existing scanners that call yyless() from OUTSIDE evallex.
+ * existing scanners that call yyless() from OUTSIDE yylex.
* One obvious solution it to make yy_act a global. I tried that, and saw
- * a 5% performance hit in a non-evallineno scanner, because yy_act is
+ * a 5% performance hit in a non-yylineno scanner, because yy_act is
* normally declared as a register variable-- so it is not worth it.
*/
#define YY_LESS_LINENO(n) \
do { \
int yyl;\
- for ( yyl = n; yyl < evalleng; ++yyl )\
- if ( evaltext[yyl] == '\n' )\
- --evallineno;\
+ for ( yyl = n; yyl < yyleng; ++yyl )\
+ if ( yytext[yyl] == '\n' )\
+ --yylineno;\
}while(0)
#define YY_LINENO_REWIND_TO(dst) \
do {\
const char *p;\
for ( p = yy_cp-1; p >= (dst); --p)\
if ( *p == '\n' )\
- --evallineno;\
+ --yylineno;\
}while(0)
/* Return all but the first "n" matched characters back to the input stream. */
#define yyless(n) \
do \
{ \
- /* Undo effects of setting up evaltext. */ \
+ /* Undo effects of setting up yytext. */ \
int yyless_macro_arg = (n); \
YY_LESS_LINENO(yyless_macro_arg);\
*yy_cp = (yy_hold_char); \
YY_RESTORE_YY_MORE_OFFSET \
(yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
- YY_DO_BEFORE_ACTION; /* set up evaltext again */ \
+ YY_DO_BEFORE_ACTION; /* set up yytext again */ \
} \
while ( 0 )
-
#define unput(c) yyunput( c, (yytext_ptr) )
#ifndef YY_STRUCT_YY_BUFFER_STATE
@@ -313,7 +547,7 @@ struct yy_buffer_state
int yy_bs_lineno; /**< The line count. */
int yy_bs_column; /**< The column count. */
-
+
/* Whether to try to fill the input buffer when we reach the
* end of it.
*/
@@ -330,8 +564,8 @@ struct yy_buffer_state
* possible backing-up.
*
* When we actually see the EOF, we change the status to "new"
- * (via evalrestart()), so that the user can continue scanning by
- * just pointing evalin at a new input file.
+ * (via yyrestart()), so that the user can continue scanning by
+ * just pointing yyin at a new input file.
*/
#define YY_BUFFER_EOF_PENDING 2
@@ -340,7 +574,6 @@ struct yy_buffer_state
/* %if-c-only Standard (non-C++) definition */
/* %not-for-header */
-
/* %if-not-reentrant */
/* Stack of input buffers. */
@@ -361,7 +594,6 @@ static YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */
#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \
? (yy_buffer_stack)[(yy_buffer_stack_top)] \
: NULL)
-
/* Same as previous macro, but useful when we know that the buffer stack is not
* NULL or when we need an lvalue. For internal use only.
*/
@@ -371,120 +603,112 @@ static YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */
/* %if-not-reentrant */
/* %not-for-header */
-
-/* yy_hold_char holds the character lost when evaltext is formed. */
+/* yy_hold_char holds the character lost when yytext is formed. */
static char yy_hold_char;
static int yy_n_chars; /* number of characters read into yy_ch_buf */
-int evalleng;
+int yyleng;
/* Points to current character in buffer. */
static char *yy_c_buf_p = NULL;
static int yy_init = 0; /* whether we need to initialize */
static int yy_start = 0; /* start state number */
-/* Flag which is used to allow evalwrap()'s to do buffer switches
- * instead of setting up a fresh evalin. A bit of a hack ...
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin. A bit of a hack ...
*/
static int yy_did_buffer_switch_on_eof;
/* %ok-for-header */
/* %endif */
-void evalrestart (FILE *input_file );
-void eval_switch_to_buffer (YY_BUFFER_STATE new_buffer );
-YY_BUFFER_STATE eval_create_buffer (FILE *file,int size );
-void eval_delete_buffer (YY_BUFFER_STATE b );
-void eval_flush_buffer (YY_BUFFER_STATE b );
-void evalpush_buffer_state (YY_BUFFER_STATE new_buffer );
-void evalpop_buffer_state (void );
-
-static void evalensure_buffer_stack (void );
-static void eval_load_buffer_state (void );
-static void eval_init_buffer (YY_BUFFER_STATE b,FILE *file );
+void yyrestart ( FILE *input_file );
+void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer );
+YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size );
+void yy_delete_buffer ( YY_BUFFER_STATE b );
+void yy_flush_buffer ( YY_BUFFER_STATE b );
+void yypush_buffer_state ( YY_BUFFER_STATE new_buffer );
+void yypop_buffer_state ( void );
-#define YY_FLUSH_BUFFER eval_flush_buffer(YY_CURRENT_BUFFER )
+static void yyensure_buffer_stack ( void );
+static void yy_load_buffer_state ( void );
+static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file );
+#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER )
-YY_BUFFER_STATE eval_scan_buffer (char *base,yy_size_t size );
-YY_BUFFER_STATE eval_scan_string (yyconst char *yy_str );
-YY_BUFFER_STATE eval_scan_bytes (yyconst char *bytes,int len );
+YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size );
+YY_BUFFER_STATE yy_scan_string ( const char *yy_str );
+YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len );
/* %endif */
-void *evalalloc (yy_size_t );
-void *evalrealloc (void *,yy_size_t );
-void evalfree (void * );
-
-#define yy_new_buffer eval_create_buffer
+void *yyalloc ( yy_size_t );
+void *yyrealloc ( void *, yy_size_t );
+void yyfree ( void * );
+#define yy_new_buffer yy_create_buffer
#define yy_set_interactive(is_interactive) \
{ \
if ( ! YY_CURRENT_BUFFER ){ \
- evalensure_buffer_stack (); \
+ yyensure_buffer_stack (); \
YY_CURRENT_BUFFER_LVALUE = \
- eval_create_buffer(evalin,YY_BUF_SIZE ); \
+ yy_create_buffer( yyin, YY_BUF_SIZE ); \
} \
YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
}
-
#define yy_set_bol(at_bol) \
{ \
if ( ! YY_CURRENT_BUFFER ){\
- evalensure_buffer_stack (); \
+ yyensure_buffer_stack (); \
YY_CURRENT_BUFFER_LVALUE = \
- eval_create_buffer(evalin,YY_BUF_SIZE ); \
+ yy_create_buffer( yyin, YY_BUF_SIZE ); \
} \
YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
}
-
#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
-/* %% [1.0] evaltext/evalin/evalout/yy_state_type/evallineno etc. def's & init go here */
+/* %% [1.0] yytext/yyin/yyout/yy_state_type/yylineno etc. def's & init go here */
/* Begin user sect3 */
#define evalwrap() (/*CONSTCOND*/1)
#define YY_SKIP_YYWRAP
#define FLEX_DEBUG
+typedef flex_uint8_t YY_CHAR;
-typedef unsigned char YY_CHAR;
-
-FILE *evalin = NULL, *evalout = NULL;
+FILE *yyin = NULL, *yyout = NULL;
typedef int yy_state_type;
-extern int evallineno;
+extern int yylineno;
+int yylineno = 1;
-int evallineno = 1;
-
-extern char *evaltext;
+extern char *yytext;
#ifdef yytext_ptr
#undef yytext_ptr
#endif
-#define yytext_ptr evaltext
+#define yytext_ptr yytext
/* %% [1.5] DFA */
/* %if-c-only Standard (non-C++) definition */
-static yy_state_type yy_get_previous_state (void );
-static yy_state_type yy_try_NUL_trans (yy_state_type current_state );
-static int yy_get_next_buffer (void );
-static void yynoreturn yy_fatal_error (yyconst char* msg );
+static yy_state_type yy_get_previous_state ( void );
+static yy_state_type yy_try_NUL_trans ( yy_state_type current_state );
+static int yy_get_next_buffer ( void );
+static void yynoreturn yy_fatal_error ( const char* msg );
/* %endif */
/* Done after the current pattern has been matched and before the
- * corresponding action - sets up evaltext.
+ * corresponding action - sets up yytext.
*/
#define YY_DO_BEFORE_ACTION \
(yytext_ptr) = yy_bp; \
-/* %% [2.0] code to fiddle evaltext and evalleng for yymore() goes here \ */\
- evalleng = (int) (yy_cp - yy_bp); \
+/* %% [2.0] code to fiddle yytext and yyleng for yymore() goes here \ */\
+ yyleng = (int) (yy_cp - yy_bp); \
(yy_hold_char) = *yy_cp; \
*yy_cp = '\0'; \
-/* %% [3.0] code to copy yytext_ptr to evaltext[] goes here, if %array \ */\
+/* %% [3.0] code to copy yytext_ptr to yytext[] goes here, if %array \ */\
(yy_c_buf_p) = yy_cp;
-
/* %% [4.0] data tables for the DFA and the user's section 1 definitions go here */
#define YY_NUM_RULES 51
#define YY_END_OF_BUFFER 52
@@ -495,7 +719,7 @@ struct yy_trans_info
flex_int32_t yy_verify;
flex_int32_t yy_nxt;
};
-static yyconst flex_int16_t yy_acclist[280] =
+static const flex_int16_t yy_acclist[280] =
{ 0,
52, 50, 51, 1, 50, 51, 2, 51, 50, 51,
44, 50, 51, 45, 50, 51, 49, 50, 51, 48,
@@ -529,7 +753,7 @@ static yyconst flex_int16_t yy_acclist[280] =
37,16390,16390, 36,16390,16390,16390, 34,16390
} ;
-static yyconst flex_int16_t yy_accept[199] =
+static const flex_int16_t yy_accept[199] =
{ 0,
1, 1, 1, 2, 4, 7, 9, 11, 14, 17,
20, 23, 25, 28, 31, 34, 36, 38, 41, 44,
@@ -555,7 +779,7 @@ static yyconst flex_int16_t yy_accept[199] =
} ;
-static yyconst YY_CHAR yy_ec[256] =
+static const YY_CHAR yy_ec[256] =
{ 0,
1, 1, 1, 1, 1, 1, 1, 1, 2, 3,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
@@ -587,7 +811,7 @@ static yyconst YY_CHAR yy_ec[256] =
1, 1, 1, 1, 1
} ;
-static yyconst YY_CHAR yy_meta[45] =
+static const YY_CHAR yy_meta[45] =
{ 0,
1, 1, 2, 1, 1, 1, 1, 1, 1, 3,
4, 4, 4, 4, 5, 1, 4, 1, 1, 1,
@@ -596,7 +820,7 @@ static yyconst YY_CHAR yy_meta[45] =
1, 1, 1, 1
} ;
-static yyconst flex_uint16_t yy_base[203] =
+static const flex_int16_t yy_base[203] =
{ 0,
0, 0, 310, 311, 307, 305, 303, 311, 311, 311,
311, 34, 311, 39, 36, 291, 289, 81, 115, 311,
@@ -623,7 +847,7 @@ static yyconst flex_uint16_t yy_base[203] =
71, 215
} ;
-static yyconst flex_int16_t yy_def[203] =
+static const flex_int16_t yy_def[203] =
{ 0,
197, 1, 197, 197, 197, 197, 198, 197, 197, 197,
197, 197, 197, 197, 14, 199, 197, 197, 18, 197,
@@ -650,7 +874,7 @@ static yyconst flex_int16_t yy_def[203] =
197, 197
} ;
-static yyconst flex_uint16_t yy_nxt[356] =
+static const flex_int16_t yy_nxt[356] =
{ 0,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 15, 15, 16, 17, 18, 19, 19, 20,
@@ -693,7 +917,7 @@ static yyconst flex_uint16_t yy_nxt[356] =
197, 197, 197, 197, 197
} ;
-static yyconst flex_int16_t yy_chk[356] =
+static const flex_int16_t yy_chk[356] =
{ 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
@@ -737,22 +961,22 @@ static yyconst flex_int16_t yy_chk[356] =
} ;
/* Table of booleans, true if rule could match eol. */
-static yyconst flex_int32_t yy_rule_can_match_eol[52] =
+static const flex_int32_t yy_rule_can_match_eol[52] =
{ 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
-extern int eval_flex_debug;
-int eval_flex_debug = 1;
+extern int yy_flex_debug;
+int yy_flex_debug = 1;
-static yyconst flex_int16_t yy_rule_linenum[51] =
+static const flex_int16_t yy_rule_linenum[51] =
{ 0,
- 82, 87, 93, 103, 109, 127, 134, 148, 149, 150,
- 151, 152, 153, 154, 155, 156, 157, 158, 159, 160,
- 161, 162, 163, 164, 165, 166, 167, 168, 169, 170,
+ 102, 107, 113, 123, 129, 147, 154, 168, 169, 170,
171, 172, 173, 174, 175, 176, 177, 178, 179, 180,
- 181, 182, 183, 184, 185, 186, 187, 188, 189, 190
+ 181, 182, 183, 184, 185, 186, 187, 188, 189, 190,
+ 191, 192, 193, 194, 195, 196, 197, 198, 199, 200,
+ 201, 202, 203, 204, 205, 206, 207, 208, 209, 210
} ;
static yy_state_type *yy_state_buf=0, *yy_state_ptr=0;
@@ -765,7 +989,7 @@ static int *yy_full_state;
#define YY_TRAILING_HEAD_MASK 0x4000
#define REJECT \
{ \
-*yy_cp = (yy_hold_char); /* undo effects of setting up evaltext */ \
+*yy_cp = (yy_hold_char); /* undo effects of setting up yytext */ \
yy_cp = (yy_full_match); /* restore poss. backed-over text */ \
(yy_lp) = (yy_full_lp); /* restore orig. accepting pos. */ \
(yy_state_ptr) = (yy_full_state); /* restore orig. state */ \
@@ -777,9 +1001,9 @@ goto find_rule; \
#define yymore() yymore_used_but_not_detected
#define YY_MORE_ADJ 0
#define YY_RESTORE_YY_MORE_OFFSET
-char *evaltext;
+char *yytext;
#line 1 "lexer.ll"
-/* Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+/* Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -794,22 +1018,30 @@ char *evaltext;
#include <asiolink/io_address.h>
#include <boost/lexical_cast.hpp>
-// Work around an incompatibility in flex (at least versions
-// 2.5.31 through 2.5.33): it generates code that does
-// not conform to C89. See Debian bug 333231
-// <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>.
-# undef evalwrap
-# define evalwrap() 1
+/* Please avoid C++ style comments (// ... eol) as they break flex 2.6.2 */
-// The location of the current token. The lexer will keep updating it. This
-// variable will be useful for logging errors.
+/* Work around an incompatibility in flex (at least versions
+ 2.5.31 through 2.5.33): it generates code that does
+ not conform to C89. See Debian bug 333231
+ <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>. */
+# undef yywrap
+# define yywrap() 1
+
+/* The location of the current token. The lexer will keep updating it. This
+ variable will be useful for logging errors. */
static isc::eval::location loc;
-// To avoid the call to exit... oops!
+namespace {
+ bool start_token_flag = false;
+ isc::eval::EvalContext::ParserType start_token_value;
+};
+
+/* To avoid the call to exit... oops! */
#define YY_FATAL_ERROR(msg) isc::eval::EvalContext::fatal(msg)
+#line 1041 "lexer.cc"
/* noyywrap disables automatic rewinding for the next file to parse. Since we
always parse only a single string, there's no need to do any wraps. And
- using evalwrap requires linking with -lfl, which provides the default evalwrap
+ using yywrap requires linking with -lfl, which provides the default yywrap
implementation that always returns 1 anyway. */
/* nounput simplifies the lexer, by removing support for putting a character
back into the input stream. We never use such capability anyway. */
@@ -826,12 +1058,13 @@ static isc::eval::location loc;
/* These are not token expressions yet, just convenience expressions that
can be used during actual token definitions. Note some can match
incorrect inputs (e.g., IP addresses) which must be checked. */
-#line 69 "lexer.ll"
-// This code run each time a pattern is matched. It updates the location
-// by moving it ahead by evalleng bytes. evalleng specifies the length of the
-// currently matched token.
+#line 76 "lexer.ll"
+/* This code run each time a pattern is matched. It updates the location
+ by moving it ahead by yyleng bytes. yyleng specifies the length of the
+ currently matched token. */
#define YY_USER_ACTION loc.columns(evalleng);
-#line 835 "lexer.cc"
+#line 1066 "lexer.cc"
+#line 1067 "lexer.cc"
#define INITIAL 0
@@ -855,7 +1088,7 @@ static isc::eval::location loc;
/* %if-reentrant */
/* %if-c-only */
-static int yy_init_globals (void );
+static int yy_init_globals ( void );
/* %endif */
/* %if-reentrant */
@@ -865,31 +1098,31 @@ static int yy_init_globals (void );
/* Accessor methods to globals.
These are made visible to non-reentrant scanners for convenience. */
-int evallex_destroy (void );
+int yylex_destroy ( void );
-int evalget_debug (void );
+int yyget_debug ( void );
-void evalset_debug (int debug_flag );
+void yyset_debug ( int debug_flag );
-YY_EXTRA_TYPE evalget_extra (void );
+YY_EXTRA_TYPE yyget_extra ( void );
-void evalset_extra (YY_EXTRA_TYPE user_defined );
+void yyset_extra ( YY_EXTRA_TYPE user_defined );
-FILE *evalget_in (void );
+FILE *yyget_in ( void );
-void evalset_in (FILE * _in_str );
+void yyset_in ( FILE * _in_str );
-FILE *evalget_out (void );
+FILE *yyget_out ( void );
-void evalset_out (FILE * _out_str );
+void yyset_out ( FILE * _out_str );
- int evalget_leng (void );
+ int yyget_leng ( void );
-char *evalget_text (void );
+char *yyget_text ( void );
-int evalget_lineno (void );
+int yyget_lineno ( void );
-void evalset_lineno (int _line_number );
+void yyset_lineno ( int _line_number );
/* %if-bison-bridge */
/* %endif */
@@ -900,14 +1133,13 @@ void evalset_lineno (int _line_number );
#ifndef YY_SKIP_YYWRAP
#ifdef __cplusplus
-extern "C" int evalwrap (void );
+extern "C" int yywrap ( void );
#else
-extern int evalwrap (void );
+extern int yywrap ( void );
#endif
#endif
/* %not-for-header */
-
#ifndef YY_NO_UNPUT
#endif
@@ -916,21 +1148,20 @@ extern int evalwrap (void );
/* %endif */
#ifndef yytext_ptr
-static void yy_flex_strncpy (char *,yyconst char *,int );
+static void yy_flex_strncpy ( char *, const char *, int );
#endif
#ifdef YY_NEED_STRLEN
-static int yy_flex_strlen (yyconst char * );
+static int yy_flex_strlen ( const char * );
#endif
#ifndef YY_NO_INPUT
/* %if-c-only Standard (non-C++) definition */
/* %not-for-header */
-
#ifdef __cplusplus
-static int yyinput (void );
+static int yyinput ( void );
#else
-static int input (void );
+static int input ( void );
#endif
/* %ok-for-header */
@@ -957,7 +1188,7 @@ static int input (void );
/* This used to be an fputs(), but since the string might contain NUL's,
* we now use fwrite().
*/
-#define ECHO do { if (fwrite( evaltext, (size_t) evalleng, 1, evalout )) {} } while (0)
+#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0)
/* %endif */
/* %if-c++-only C++ definition */
/* %endif */
@@ -972,20 +1203,20 @@ static int input (void );
if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
{ \
int c = '*'; \
- size_t n; \
+ int n; \
for ( n = 0; n < max_size && \
- (c = getc( evalin )) != EOF && c != '\n'; ++n ) \
+ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
buf[n] = (char) c; \
if ( c == '\n' ) \
buf[n++] = (char) c; \
- if ( c == EOF && ferror( evalin ) ) \
+ if ( c == EOF && ferror( yyin ) ) \
YY_FATAL_ERROR( "input in flex scanner failed" ); \
result = n; \
} \
else \
{ \
errno=0; \
- while ( (result = (int) fread(buf, 1, max_size, evalin))==0 && ferror(evalin)) \
+ while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \
{ \
if( errno != EINTR) \
{ \
@@ -993,7 +1224,7 @@ static int input (void );
break; \
} \
errno=0; \
- clearerr(evalin); \
+ clearerr(yyin); \
} \
}\
\
@@ -1026,11 +1257,9 @@ static int input (void );
/* %if-tables-serialization structures and prototypes */
/* %not-for-header */
-
/* %ok-for-header */
/* %not-for-header */
-
/* %tables-yydmap generated elements */
/* %endif */
/* end tables serialization structures and prototypes */
@@ -1044,15 +1273,15 @@ static int input (void );
#define YY_DECL_IS_OURS 1
/* %if-c-only Standard (non-C++) definition */
-extern int evallex (void);
+extern int yylex (void);
-#define YY_DECL int evallex (void)
+#define YY_DECL int yylex (void)
/* %endif */
/* %if-c++-only C++ definition */
/* %endif */
#endif /* !YY_DECL */
-/* Code executed at the beginning of each rule, after evaltext and evalleng
+/* Code executed at the beginning of each rule, after yytext and yyleng
* have been set up.
*/
#ifndef YY_USER_ACTION
@@ -1069,7 +1298,6 @@ extern int evallex (void);
YY_USER_ACTION
/* %not-for-header */
-
/** The main scanner function which does all the work.
*/
YY_DECL
@@ -1088,54 +1316,67 @@ YY_DECL
/* Create the reject buffer large enough to save one state per allowed character. */
if ( ! (yy_state_buf) )
- (yy_state_buf) = (yy_state_type *)evalalloc(YY_STATE_BUF_SIZE );
+ (yy_state_buf) = (yy_state_type *)yyalloc(YY_STATE_BUF_SIZE );
if ( ! (yy_state_buf) )
- YY_FATAL_ERROR( "out of dynamic memory in evallex()" );
+ YY_FATAL_ERROR( "out of dynamic memory in yylex()" );
if ( ! (yy_start) )
(yy_start) = 1; /* first start state */
- if ( ! evalin )
+ if ( ! yyin )
/* %if-c-only */
- evalin = stdin;
+ yyin = stdin;
/* %endif */
/* %if-c++-only */
/* %endif */
- if ( ! evalout )
+ if ( ! yyout )
/* %if-c-only */
- evalout = stdout;
+ yyout = stdout;
/* %endif */
/* %if-c++-only */
/* %endif */
if ( ! YY_CURRENT_BUFFER ) {
- evalensure_buffer_stack ();
+ yyensure_buffer_stack ();
YY_CURRENT_BUFFER_LVALUE =
- eval_create_buffer(evalin,YY_BUF_SIZE );
+ yy_create_buffer( yyin, YY_BUF_SIZE );
}
- eval_load_buffer_state( );
+ yy_load_buffer_state( );
}
{
/* %% [7.0] user's declarations go here */
-#line 75 "lexer.ll"
+#line 82 "lexer.ll"
- // Code run each time evallex is called.
+#line 86 "lexer.ll"
+ /* Code run each time evallex is called. */
loc.step();
+ if (start_token_flag) {
+ start_token_flag = false;
+ switch (start_token_value) {
+ case EvalContext::PARSER_BOOL:
+ return isc::eval::EvalParser::make_TOPLEVEL_BOOL(loc);
+ default:
+ case EvalContext::PARSER_STRING:
+ return isc::eval::EvalParser::make_TOPLEVEL_STRING(loc);
+ }
+ }
-#line 1132 "lexer.cc"
+
+
+#line 1372 "lexer.cc"
while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */
{
/* %% [8.0] yymore()-related code goes here */
yy_cp = (yy_c_buf_p);
- /* Support of evaltext. */
+ /* Support of yytext. */
*yy_cp = (yy_hold_char);
/* yy_bp points to the position in yy_ch_buf of the start of
@@ -1157,9 +1398,9 @@ yy_match:
{
yy_current_state = (int) yy_def[yy_current_state];
if ( yy_current_state >= 198 )
- yy_c = yy_meta[(unsigned int) yy_c];
+ yy_c = yy_meta[yy_c];
}
- yy_current_state = yy_nxt[yy_base[yy_current_state] + (flex_int16_t) yy_c];
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
*(yy_state_ptr)++ = yy_current_state;
++yy_cp;
}
@@ -1207,31 +1448,31 @@ find_rule: /* we branch to this label when backing up */
YY_DO_BEFORE_ACTION;
-/* %% [11.0] code for evallineno update goes here */
+/* %% [11.0] code for yylineno update goes here */
if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] )
{
- yy_size_t yyl;
- for ( yyl = 0; yyl < evalleng; ++yyl )
- if ( evaltext[yyl] == '\n' )
-
- evallineno++;
+ int yyl;
+ for ( yyl = 0; yyl < yyleng; ++yyl )
+ if ( yytext[yyl] == '\n' )
+
+ yylineno++;
;
}
do_action: /* This label is used only to access EOF actions. */
/* %% [12.0] debug code goes here */
- if ( eval_flex_debug )
+ if ( yy_flex_debug )
{
if ( yy_act == 0 )
fprintf( stderr, "--scanner backing up\n" );
else if ( yy_act < 51 )
fprintf( stderr, "--accepting rule at line %ld (\"%s\")\n",
- (long)yy_rule_linenum[yy_act], evaltext );
+ (long)yy_rule_linenum[yy_act], yytext );
else if ( yy_act == 51 )
fprintf( stderr, "--accepting default rule (\"%s\")\n",
- evaltext );
+ yytext );
else if ( yy_act == 52 )
fprintf( stderr, "--(end of buffer or a NUL)\n" );
else
@@ -1243,29 +1484,29 @@ do_action: /* This label is used only to access EOF actions. */
/* %% [13.0] actions go here */
case 1:
YY_RULE_SETUP
-#line 82 "lexer.ll"
+#line 102 "lexer.ll"
{
- // Ok, we found a with space. Let's ignore it and update loc variable.
+ /* Ok, we found a with space. Let's ignore it and update loc variable. */
loc.step();
}
YY_BREAK
case 2:
/* rule 2 can match eol */
YY_RULE_SETUP
-#line 87 "lexer.ll"
+#line 107 "lexer.ll"
{
- // Newline found. Let's update the location and continue.
+ /* Newline found. Let's update the location and continue. */
loc.lines(evalleng);
loc.step();
}
YY_BREAK
case 3:
YY_RULE_SETUP
-#line 93 "lexer.ll"
+#line 113 "lexer.ll"
{
- // A string has been matched. It contains the actual string and single quotes.
- // We need to get those quotes out of the way and just use its content, e.g.
- // for 'foo' we should get foo
+ /* A string has been matched. It contains the actual string and single quotes.
+ We need to get those quotes out of the way and just use its content, e.g.
+ for 'foo' we should get foo */
std::string tmp(evaltext+1);
tmp.resize(tmp.size() - 1);
@@ -1274,53 +1515,53 @@ YY_RULE_SETUP
YY_BREAK
case 4:
YY_RULE_SETUP
-#line 103 "lexer.ll"
+#line 123 "lexer.ll"
{
- // A hex string has been matched. It contains the '0x' or '0X' header
- // followed by at least one hexadecimal digit.
+ /* A hex string has been matched. It contains the '0x' or '0X' header
+ followed by at least one hexadecimal digit. */
return isc::eval::EvalParser::make_HEXSTRING(evaltext, loc);
}
YY_BREAK
case 5:
YY_RULE_SETUP
-#line 109 "lexer.ll"
+#line 129 "lexer.ll"
{
- // An integer was found.
+ /* An integer was found. */
std::string tmp(evaltext);
try {
- // In substring we want to use negative values (e.g. -1).
- // In enterprise-id we need to use values up to 0xffffffff.
- // To cover both of those use cases, we need at least
- // int64_t.
+ /* In substring we want to use negative values (e.g. -1).
+ In enterprise-id we need to use values up to 0xffffffff.
+ To cover both of those use cases, we need at least
+ int64_t. */
static_cast<void>(boost::lexical_cast<int64_t>(tmp));
} catch (const boost::bad_lexical_cast &) {
driver.error(loc, "Failed to convert " + tmp + " to an integer.");
}
- // The parser needs the string form as double conversion is no lossless
+ /* The parser needs the string form as double conversion is no lossless */
return isc::eval::EvalParser::make_INTEGER(tmp, loc);
}
YY_BREAK
case 6:
/* rule 6 can match eol */
YY_RULE_SETUP
-#line 127 "lexer.ll"
+#line 147 "lexer.ll"
{
- // This string specifies option name starting with a letter
- // and further containing letters, digits, hyphens and
- // underscores and finishing by letters or digits.
+ /* This string specifies option name starting with a letter
+ and further containing letters, digits, hyphens and
+ underscores and finishing by letters or digits. */
return isc::eval::EvalParser::make_OPTION_NAME(evaltext, loc);
}
YY_BREAK
case 7:
YY_RULE_SETUP
-#line 134 "lexer.ll"
+#line 154 "lexer.ll"
{
- // IPv4 or IPv6 address
+ /* IPv4 or IPv6 address */
std::string tmp(evaltext);
- // Some incorrect addresses can match so we have to check.
+ /* Some incorrect addresses can match so we have to check. */
try {
isc::asiolink::IOAddress ip(tmp);
} catch (...) {
@@ -1332,229 +1573,229 @@ YY_RULE_SETUP
YY_BREAK
case 8:
YY_RULE_SETUP
-#line 148 "lexer.ll"
+#line 168 "lexer.ll"
return isc::eval::EvalParser::make_EQUAL(loc);
YY_BREAK
case 9:
YY_RULE_SETUP
-#line 149 "lexer.ll"
+#line 169 "lexer.ll"
return isc::eval::EvalParser::make_OPTION(loc);
YY_BREAK
case 10:
YY_RULE_SETUP
-#line 150 "lexer.ll"
+#line 170 "lexer.ll"
return isc::eval::EvalParser::make_RELAY4(loc);
YY_BREAK
case 11:
YY_RULE_SETUP
-#line 151 "lexer.ll"
+#line 171 "lexer.ll"
return isc::eval::EvalParser::make_RELAY6(loc);
YY_BREAK
case 12:
YY_RULE_SETUP
-#line 152 "lexer.ll"
+#line 172 "lexer.ll"
return isc::eval::EvalParser::make_PEERADDR(loc);
YY_BREAK
case 13:
YY_RULE_SETUP
-#line 153 "lexer.ll"
+#line 173 "lexer.ll"
return isc::eval::EvalParser::make_LINKADDR(loc);
YY_BREAK
case 14:
YY_RULE_SETUP
-#line 154 "lexer.ll"
+#line 174 "lexer.ll"
return isc::eval::EvalParser::make_TEXT(loc);
YY_BREAK
case 15:
YY_RULE_SETUP
-#line 155 "lexer.ll"
+#line 175 "lexer.ll"
return isc::eval::EvalParser::make_HEX(loc);
YY_BREAK
case 16:
YY_RULE_SETUP
-#line 156 "lexer.ll"
+#line 176 "lexer.ll"
return isc::eval::EvalParser::make_EXISTS(loc);
YY_BREAK
case 17:
YY_RULE_SETUP
-#line 157 "lexer.ll"
+#line 177 "lexer.ll"
return isc::eval::EvalParser::make_PKT(loc);
YY_BREAK
case 18:
YY_RULE_SETUP
-#line 158 "lexer.ll"
+#line 178 "lexer.ll"
return isc::eval::EvalParser::make_IFACE(loc);
YY_BREAK
case 19:
YY_RULE_SETUP
-#line 159 "lexer.ll"
+#line 179 "lexer.ll"
return isc::eval::EvalParser::make_SRC(loc);
YY_BREAK
case 20:
YY_RULE_SETUP
-#line 160 "lexer.ll"
+#line 180 "lexer.ll"
return isc::eval::EvalParser::make_DST(loc);
YY_BREAK
case 21:
YY_RULE_SETUP
-#line 161 "lexer.ll"
+#line 181 "lexer.ll"
return isc::eval::EvalParser::make_LEN(loc);
YY_BREAK
case 22:
YY_RULE_SETUP
-#line 162 "lexer.ll"
+#line 182 "lexer.ll"
return isc::eval::EvalParser::make_PKT4(loc);
YY_BREAK
case 23:
YY_RULE_SETUP
-#line 163 "lexer.ll"
+#line 183 "lexer.ll"
return isc::eval::EvalParser::make_CHADDR(loc);
YY_BREAK
case 24:
YY_RULE_SETUP
-#line 164 "lexer.ll"
+#line 184 "lexer.ll"
return isc::eval::EvalParser::make_HLEN(loc);
YY_BREAK
case 25:
YY_RULE_SETUP
-#line 165 "lexer.ll"
+#line 185 "lexer.ll"
return isc::eval::EvalParser::make_HTYPE(loc);
YY_BREAK
case 26:
YY_RULE_SETUP
-#line 166 "lexer.ll"
+#line 186 "lexer.ll"
return isc::eval::EvalParser::make_CIADDR(loc);
YY_BREAK
case 27:
YY_RULE_SETUP
-#line 167 "lexer.ll"
+#line 187 "lexer.ll"
return isc::eval::EvalParser::make_GIADDR(loc);
YY_BREAK
case 28:
YY_RULE_SETUP
-#line 168 "lexer.ll"
+#line 188 "lexer.ll"
return isc::eval::EvalParser::make_YIADDR(loc);
YY_BREAK
case 29:
YY_RULE_SETUP
-#line 169 "lexer.ll"
+#line 189 "lexer.ll"
return isc::eval::EvalParser::make_SIADDR(loc);
YY_BREAK
case 30:
YY_RULE_SETUP
-#line 170 "lexer.ll"
+#line 190 "lexer.ll"
return isc::eval::EvalParser::make_PKT6(loc);
YY_BREAK
case 31:
YY_RULE_SETUP
-#line 171 "lexer.ll"
+#line 191 "lexer.ll"
return isc::eval::EvalParser::make_MSGTYPE(loc);
YY_BREAK
case 32:
YY_RULE_SETUP
-#line 172 "lexer.ll"
+#line 192 "lexer.ll"
return isc::eval::EvalParser::make_TRANSID(loc);
YY_BREAK
case 33:
YY_RULE_SETUP
-#line 173 "lexer.ll"
+#line 193 "lexer.ll"
return isc::eval::EvalParser::make_VENDOR(loc);
YY_BREAK
case 34:
YY_RULE_SETUP
-#line 174 "lexer.ll"
+#line 194 "lexer.ll"
return isc::eval::EvalParser::make_VENDOR_CLASS(loc);
YY_BREAK
case 35:
YY_RULE_SETUP
-#line 175 "lexer.ll"
+#line 195 "lexer.ll"
return isc::eval::EvalParser::make_DATA(loc);
YY_BREAK
case 36:
YY_RULE_SETUP
-#line 176 "lexer.ll"
+#line 196 "lexer.ll"
return isc::eval::EvalParser::make_ENTERPRISE(loc);
YY_BREAK
case 37:
YY_RULE_SETUP
-#line 177 "lexer.ll"
+#line 197 "lexer.ll"
return isc::eval::EvalParser::make_SUBSTRING(loc);
YY_BREAK
case 38:
YY_RULE_SETUP
-#line 178 "lexer.ll"
+#line 198 "lexer.ll"
return isc::eval::EvalParser::make_ALL(loc);
YY_BREAK
case 39:
YY_RULE_SETUP
-#line 179 "lexer.ll"
+#line 199 "lexer.ll"
return isc::eval::EvalParser::make_CONCAT(loc);
YY_BREAK
case 40:
YY_RULE_SETUP
-#line 180 "lexer.ll"
+#line 200 "lexer.ll"
return isc::eval::EvalParser::make_NOT(loc);
YY_BREAK
case 41:
YY_RULE_SETUP
-#line 181 "lexer.ll"
+#line 201 "lexer.ll"
return isc::eval::EvalParser::make_AND(loc);
YY_BREAK
case 42:
YY_RULE_SETUP
-#line 182 "lexer.ll"
+#line 202 "lexer.ll"
return isc::eval::EvalParser::make_OR(loc);
YY_BREAK
case 43:
YY_RULE_SETUP
-#line 183 "lexer.ll"
+#line 203 "lexer.ll"
return isc::eval::EvalParser::make_DOT(loc);
YY_BREAK
case 44:
YY_RULE_SETUP
-#line 184 "lexer.ll"
+#line 204 "lexer.ll"
return isc::eval::EvalParser::make_LPAREN(loc);
YY_BREAK
case 45:
YY_RULE_SETUP
-#line 185 "lexer.ll"
+#line 205 "lexer.ll"
return isc::eval::EvalParser::make_RPAREN(loc);
YY_BREAK
case 46:
YY_RULE_SETUP
-#line 186 "lexer.ll"
+#line 206 "lexer.ll"
return isc::eval::EvalParser::make_LBRACKET(loc);
YY_BREAK
case 47:
YY_RULE_SETUP
-#line 187 "lexer.ll"
+#line 207 "lexer.ll"
return isc::eval::EvalParser::make_RBRACKET(loc);
YY_BREAK
case 48:
YY_RULE_SETUP
-#line 188 "lexer.ll"
+#line 208 "lexer.ll"
return isc::eval::EvalParser::make_COMA(loc);
YY_BREAK
case 49:
YY_RULE_SETUP
-#line 189 "lexer.ll"
+#line 209 "lexer.ll"
return isc::eval::EvalParser::make_ANY(loc);
YY_BREAK
case 50:
YY_RULE_SETUP
-#line 190 "lexer.ll"
+#line 210 "lexer.ll"
driver.error (loc, "Invalid character: " + std::string(evaltext));
YY_BREAK
case YY_STATE_EOF(INITIAL):
-#line 191 "lexer.ll"
+#line 211 "lexer.ll"
return isc::eval::EvalParser::make_END(loc);
YY_BREAK
case 51:
YY_RULE_SETUP
-#line 192 "lexer.ll"
+#line 212 "lexer.ll"
ECHO;
YY_BREAK
-#line 1558 "lexer.cc"
+#line 1798 "lexer.cc"
case YY_END_OF_BUFFER:
{
@@ -1569,8 +1810,8 @@ ECHO;
{
/* We're scanning a new file or input source. It's
* possible that this happened because the user
- * just pointed evalin at a new source and called
- * evallex(). If so, then we have to assure
+ * just pointed yyin at a new source and called
+ * yylex(). If so, then we have to assure
* consistency between YY_CURRENT_BUFFER and our
* globals. Here is the right place to do so, because
* this is the first action (other than possibly a
@@ -1578,7 +1819,7 @@ ECHO;
*/
(yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
/* %if-c-only */
- YY_CURRENT_BUFFER_LVALUE->yy_input_file = evalin;
+ YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
/* %endif */
/* %if-c++-only */
/* %endif */
@@ -1635,11 +1876,11 @@ ECHO;
{
(yy_did_buffer_switch_on_eof) = 0;
- if ( evalwrap( ) )
+ if ( yywrap( ) )
{
/* Note: because we've taken care in
* yy_get_next_buffer() to have set up
- * evaltext, we can now set up
+ * yytext, we can now set up
* yy_c_buf_p so that if some total
* hoser (like flex itself) wants to
* call the scanner after we return the
@@ -1689,12 +1930,11 @@ ECHO;
} /* end of action switch */
} /* end of scanning one token */
} /* end of user's declarations */
-} /* end of evallex */
+} /* end of yylex */
/* %ok-for-header */
/* %if-c++-only */
/* %not-for-header */
-
/* %ok-for-header */
/* %endif */
@@ -1714,7 +1954,7 @@ static int yy_get_next_buffer (void)
{
char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
char *source = (yytext_ptr);
- yy_size_t number_to_move, i;
+ int number_to_move, i;
int ret_val;
if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] )
@@ -1743,7 +1983,7 @@ static int yy_get_next_buffer (void)
/* Try to read more data. */
/* First move last chars to start of buffer. */
- number_to_move = (yy_size_t) ((yy_c_buf_p) - (yytext_ptr)) - 1;
+ number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1);
for ( i = 0; i < number_to_move; ++i )
*(dest++) = *(source++);
@@ -1782,7 +2022,7 @@ static int yy_get_next_buffer (void)
if ( number_to_move == YY_MORE_ADJ )
{
ret_val = EOB_ACT_END_OF_FILE;
- evalrestart(evalin );
+ yyrestart( yyin );
}
else
@@ -1796,12 +2036,15 @@ static int yy_get_next_buffer (void)
else
ret_val = EOB_ACT_CONTINUE_SCAN;
- if ((int) ((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) {
+ if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) {
/* Extend the array by 50%, plus the number we really need. */
int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1);
- YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) evalrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size );
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc(
+ (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size );
if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" );
+ /* "- 2" to take care of EOB's */
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2);
}
(yy_n_chars) += number_to_move;
@@ -1817,7 +2060,6 @@ static int yy_get_next_buffer (void)
/* %if-c-only */
/* %not-for-header */
-
static yy_state_type yy_get_previous_state (void)
/* %endif */
/* %if-c++-only */
@@ -1840,9 +2082,9 @@ static int yy_get_next_buffer (void)
{
yy_current_state = (int) yy_def[yy_current_state];
if ( yy_current_state >= 198 )
- yy_c = yy_meta[(unsigned int) yy_c];
+ yy_c = yy_meta[yy_c];
}
- yy_current_state = yy_nxt[yy_base[yy_current_state] + (flex_int16_t) yy_c];
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
*(yy_state_ptr)++ = yy_current_state;
}
@@ -1868,9 +2110,9 @@ static int yy_get_next_buffer (void)
{
yy_current_state = (int) yy_def[yy_current_state];
if ( yy_current_state >= 198 )
- yy_c = yy_meta[(unsigned int) yy_c];
+ yy_c = yy_meta[yy_c];
}
- yy_current_state = yy_nxt[yy_base[yy_current_state] + (flex_int16_t) yy_c];
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
yy_is_jam = (yy_current_state == 197);
if ( ! yy_is_jam )
*(yy_state_ptr)++ = yy_current_state;
@@ -1912,7 +2154,7 @@ static int yy_get_next_buffer (void)
else
{ /* need more input */
- int offset = (yy_c_buf_p) - (yytext_ptr);
+ int offset = (int) ((yy_c_buf_p) - (yytext_ptr));
++(yy_c_buf_p);
switch ( yy_get_next_buffer( ) )
@@ -1929,13 +2171,13 @@ static int yy_get_next_buffer (void)
*/
/* Reset buffer status. */
- evalrestart(evalin );
+ yyrestart( yyin );
/*FALLTHROUGH*/
case EOB_ACT_END_OF_FILE:
{
- if ( evalwrap( ) )
+ if ( yywrap( ) )
return 0;
if ( ! (yy_did_buffer_switch_on_eof) )
@@ -1955,13 +2197,13 @@ static int yy_get_next_buffer (void)
}
c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */
- *(yy_c_buf_p) = '\0'; /* preserve evaltext */
+ *(yy_c_buf_p) = '\0'; /* preserve yytext */
(yy_hold_char) = *++(yy_c_buf_p);
-/* %% [19.0] update BOL and evallineno */
+/* %% [19.0] update BOL and yylineno */
if ( c == '\n' )
-
- evallineno++;
+
+ yylineno++;
;
return c;
@@ -1976,20 +2218,20 @@ static int yy_get_next_buffer (void)
* @note This function does not reset the start condition to @c INITIAL .
*/
/* %if-c-only */
- void evalrestart (FILE * input_file )
+ void yyrestart (FILE * input_file )
/* %endif */
/* %if-c++-only */
/* %endif */
{
if ( ! YY_CURRENT_BUFFER ){
- evalensure_buffer_stack ();
+ yyensure_buffer_stack ();
YY_CURRENT_BUFFER_LVALUE =
- eval_create_buffer(evalin,YY_BUF_SIZE );
+ yy_create_buffer( yyin, YY_BUF_SIZE );
}
- eval_init_buffer(YY_CURRENT_BUFFER,input_file );
- eval_load_buffer_state( );
+ yy_init_buffer( YY_CURRENT_BUFFER, input_file );
+ yy_load_buffer_state( );
}
/* %if-c++-only */
@@ -2000,7 +2242,7 @@ static int yy_get_next_buffer (void)
*
*/
/* %if-c-only */
- void eval_switch_to_buffer (YY_BUFFER_STATE new_buffer )
+ void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer )
/* %endif */
/* %if-c++-only */
/* %endif */
@@ -2008,10 +2250,10 @@ static int yy_get_next_buffer (void)
/* TODO. We should be able to replace this entire function body
* with
- * evalpop_buffer_state();
- * evalpush_buffer_state(new_buffer);
+ * yypop_buffer_state();
+ * yypush_buffer_state(new_buffer);
*/
- evalensure_buffer_stack ();
+ yyensure_buffer_stack ();
if ( YY_CURRENT_BUFFER == new_buffer )
return;
@@ -2024,18 +2266,18 @@ static int yy_get_next_buffer (void)
}
YY_CURRENT_BUFFER_LVALUE = new_buffer;
- eval_load_buffer_state( );
+ yy_load_buffer_state( );
/* We don't actually know whether we did this switch during
- * EOF (evalwrap()) processing, but the only time this flag
- * is looked at is after evalwrap() is called, so it's safe
+ * EOF (yywrap()) processing, but the only time this flag
+ * is looked at is after yywrap() is called, so it's safe
* to go ahead and always set it.
*/
(yy_did_buffer_switch_on_eof) = 1;
}
/* %if-c-only */
-static void eval_load_buffer_state (void)
+static void yy_load_buffer_state (void)
/* %endif */
/* %if-c++-only */
/* %endif */
@@ -2043,7 +2285,7 @@ static void eval_load_buffer_state (void)
(yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
(yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
/* %if-c-only */
- evalin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+ yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
/* %endif */
/* %if-c++-only */
/* %endif */
@@ -2057,29 +2299,29 @@ static void eval_load_buffer_state (void)
* @return the allocated buffer state.
*/
/* %if-c-only */
- YY_BUFFER_STATE eval_create_buffer (FILE * file, int size )
+ YY_BUFFER_STATE yy_create_buffer (FILE * file, int size )
/* %endif */
/* %if-c++-only */
/* %endif */
{
YY_BUFFER_STATE b;
- b = (YY_BUFFER_STATE) evalalloc(sizeof( struct yy_buffer_state ) );
+ b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) );
if ( ! b )
- YY_FATAL_ERROR( "out of dynamic memory in eval_create_buffer()" );
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
- b->yy_buf_size = (yy_size_t)size;
+ b->yy_buf_size = size;
/* yy_ch_buf has to be 2 characters longer than the size given because
* we need to put in 2 end-of-buffer characters.
*/
- b->yy_ch_buf = (char *) evalalloc(b->yy_buf_size + 2 );
+ b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) );
if ( ! b->yy_ch_buf )
- YY_FATAL_ERROR( "out of dynamic memory in eval_create_buffer()" );
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
b->yy_is_our_buffer = 1;
- eval_init_buffer(b,file );
+ yy_init_buffer( b, file );
return b;
}
@@ -2088,11 +2330,11 @@ static void eval_load_buffer_state (void)
/* %endif */
/** Destroy the buffer.
- * @param b a buffer created with eval_create_buffer()
+ * @param b a buffer created with yy_create_buffer()
*
*/
/* %if-c-only */
- void eval_delete_buffer (YY_BUFFER_STATE b )
+ void yy_delete_buffer (YY_BUFFER_STATE b )
/* %endif */
/* %if-c++-only */
/* %endif */
@@ -2105,17 +2347,17 @@ static void eval_load_buffer_state (void)
YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
if ( b->yy_is_our_buffer )
- evalfree((void *) b->yy_ch_buf );
+ yyfree( (void *) b->yy_ch_buf );
- evalfree((void *) b );
+ yyfree( (void *) b );
}
/* Initializes or reinitializes a buffer.
* This function is sometimes called more than once on the same buffer,
- * such as during a evalrestart() or at EOF.
+ * such as during a yyrestart() or at EOF.
*/
/* %if-c-only */
- static void eval_init_buffer (YY_BUFFER_STATE b, FILE * file )
+ static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file )
/* %endif */
/* %if-c++-only */
/* %endif */
@@ -2123,7 +2365,7 @@ static void eval_load_buffer_state (void)
{
int oerrno = errno;
- eval_flush_buffer(b );
+ yy_flush_buffer( b );
/* %if-c-only */
b->yy_input_file = file;
@@ -2132,8 +2374,8 @@ static void eval_load_buffer_state (void)
/* %endif */
b->yy_fill_buffer = 1;
- /* If b is the current buffer, then eval_init_buffer was _probably_
- * called from evalrestart() or through yy_get_next_buffer.
+ /* If b is the current buffer, then yy_init_buffer was _probably_
+ * called from yyrestart() or through yy_get_next_buffer.
* In that case, we don't want to reset the lineno or column.
*/
if (b != YY_CURRENT_BUFFER){
@@ -2156,7 +2398,7 @@ static void eval_load_buffer_state (void)
*
*/
/* %if-c-only */
- void eval_flush_buffer (YY_BUFFER_STATE b )
+ void yy_flush_buffer (YY_BUFFER_STATE b )
/* %endif */
/* %if-c++-only */
/* %endif */
@@ -2179,7 +2421,7 @@ static void eval_load_buffer_state (void)
b->yy_buffer_status = YY_BUFFER_NEW;
if ( b == YY_CURRENT_BUFFER )
- eval_load_buffer_state( );
+ yy_load_buffer_state( );
}
/* %if-c-or-c++ */
@@ -2190,7 +2432,7 @@ static void eval_load_buffer_state (void)
*
*/
/* %if-c-only */
-void evalpush_buffer_state (YY_BUFFER_STATE new_buffer )
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer )
/* %endif */
/* %if-c++-only */
/* %endif */
@@ -2198,9 +2440,9 @@ void evalpush_buffer_state (YY_BUFFER_STATE new_buffer )
if (new_buffer == NULL)
return;
- evalensure_buffer_stack();
+ yyensure_buffer_stack();
- /* This block is copied from eval_switch_to_buffer. */
+ /* This block is copied from yy_switch_to_buffer. */
if ( YY_CURRENT_BUFFER )
{
/* Flush out information for old buffer. */
@@ -2214,8 +2456,8 @@ void evalpush_buffer_state (YY_BUFFER_STATE new_buffer )
(yy_buffer_stack_top)++;
YY_CURRENT_BUFFER_LVALUE = new_buffer;
- /* copied from eval_switch_to_buffer. */
- eval_load_buffer_state( );
+ /* copied from yy_switch_to_buffer. */
+ yy_load_buffer_state( );
(yy_did_buffer_switch_on_eof) = 1;
}
/* %endif */
@@ -2226,7 +2468,7 @@ void evalpush_buffer_state (YY_BUFFER_STATE new_buffer )
*
*/
/* %if-c-only */
-void evalpop_buffer_state (void)
+void yypop_buffer_state (void)
/* %endif */
/* %if-c++-only */
/* %endif */
@@ -2234,13 +2476,13 @@ void evalpop_buffer_state (void)
if (!YY_CURRENT_BUFFER)
return;
- eval_delete_buffer(YY_CURRENT_BUFFER );
+ yy_delete_buffer(YY_CURRENT_BUFFER );
YY_CURRENT_BUFFER_LVALUE = NULL;
if ((yy_buffer_stack_top) > 0)
--(yy_buffer_stack_top);
if (YY_CURRENT_BUFFER) {
- eval_load_buffer_state( );
+ yy_load_buffer_state( );
(yy_did_buffer_switch_on_eof) = 1;
}
}
@@ -2251,12 +2493,12 @@ void evalpop_buffer_state (void)
* Guarantees space for at least one push.
*/
/* %if-c-only */
-static void evalensure_buffer_stack (void)
+static void yyensure_buffer_stack (void)
/* %endif */
/* %if-c++-only */
/* %endif */
{
- int num_to_alloc;
+ yy_size_t num_to_alloc;
if (!(yy_buffer_stack)) {
@@ -2265,14 +2507,14 @@ static void evalensure_buffer_stack (void)
* immediate realloc on the next call.
*/
num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */
- (yy_buffer_stack) = (struct yy_buffer_state**)evalalloc
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc
(num_to_alloc * sizeof(struct yy_buffer_state*)
);
if ( ! (yy_buffer_stack) )
- YY_FATAL_ERROR( "out of dynamic memory in evalensure_buffer_stack()" );
-
+ YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*));
-
+
(yy_buffer_stack_max) = num_to_alloc;
(yy_buffer_stack_top) = 0;
return;
@@ -2284,12 +2526,12 @@ static void evalensure_buffer_stack (void)
yy_size_t grow_size = 8 /* arbitrary grow size */;
num_to_alloc = (yy_buffer_stack_max) + grow_size;
- (yy_buffer_stack) = (struct yy_buffer_state**)evalrealloc
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc
((yy_buffer_stack),
num_to_alloc * sizeof(struct yy_buffer_state*)
);
if ( ! (yy_buffer_stack) )
- YY_FATAL_ERROR( "out of dynamic memory in evalensure_buffer_stack()" );
+ YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
/* zero only the new slots.*/
memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*));
@@ -2303,9 +2545,9 @@ static void evalensure_buffer_stack (void)
* @param base the character buffer
* @param size the size in bytes of the character buffer
*
- * @return the newly allocated buffer state object.
+ * @return the newly allocated buffer state object.
*/
-YY_BUFFER_STATE eval_scan_buffer (char * base, yy_size_t size )
+YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size )
{
YY_BUFFER_STATE b;
@@ -2315,11 +2557,11 @@ YY_BUFFER_STATE eval_scan_buffer (char * base, yy_size_t size )
/* They forgot to leave room for the EOB's. */
return NULL;
- b = (YY_BUFFER_STATE) evalalloc(sizeof( struct yy_buffer_state ) );
+ b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) );
if ( ! b )
- YY_FATAL_ERROR( "out of dynamic memory in eval_scan_buffer()" );
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
- b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */
+ b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */
b->yy_buf_pos = b->yy_ch_buf = base;
b->yy_is_our_buffer = 0;
b->yy_input_file = NULL;
@@ -2329,57 +2571,57 @@ YY_BUFFER_STATE eval_scan_buffer (char * base, yy_size_t size )
b->yy_fill_buffer = 0;
b->yy_buffer_status = YY_BUFFER_NEW;
- eval_switch_to_buffer(b );
+ yy_switch_to_buffer( b );
return b;
}
/* %endif */
/* %if-c-only */
-/** Setup the input buffer state to scan a string. The next call to evallex() will
+/** Setup the input buffer state to scan a string. The next call to yylex() will
* scan from a @e copy of @a str.
* @param yystr a NUL-terminated string to scan
*
* @return the newly allocated buffer state object.
* @note If you want to scan bytes that may contain NUL values, then use
- * eval_scan_bytes() instead.
+ * yy_scan_bytes() instead.
*/
-YY_BUFFER_STATE eval_scan_string (yyconst char * yystr )
+YY_BUFFER_STATE yy_scan_string (const char * yystr )
{
- return eval_scan_bytes(yystr,(int) strlen(yystr) );
+ return yy_scan_bytes( yystr, (int) strlen(yystr) );
}
/* %endif */
/* %if-c-only */
-/** Setup the input buffer state to scan the given bytes. The next call to evallex() will
+/** Setup the input buffer state to scan the given bytes. The next call to yylex() will
* scan from a @e copy of @a bytes.
* @param yybytes the byte buffer to scan
* @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
*
* @return the newly allocated buffer state object.
*/
-YY_BUFFER_STATE eval_scan_bytes (yyconst char * yybytes, int _yybytes_len )
+YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len )
{
YY_BUFFER_STATE b;
char *buf;
yy_size_t n;
- yy_size_t i;
+ int i;
/* Get memory for full buffer, including space for trailing EOB's. */
- n = (yy_size_t) _yybytes_len + 2;
- buf = (char *) evalalloc(n );
+ n = (yy_size_t) (_yybytes_len + 2);
+ buf = (char *) yyalloc( n );
if ( ! buf )
- YY_FATAL_ERROR( "out of dynamic memory in eval_scan_bytes()" );
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
for ( i = 0; i < _yybytes_len; ++i )
buf[i] = yybytes[i];
buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
- b = eval_scan_buffer(buf,n );
+ b = yy_scan_buffer( buf, n );
if ( ! b )
- YY_FATAL_ERROR( "bad buffer in eval_scan_bytes()" );
+ YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
/* It's okay to grow etc. this buffer, and we should throw it
* away when we're done.
@@ -2395,9 +2637,9 @@ YY_BUFFER_STATE eval_scan_bytes (yyconst char * yybytes, int _yybytes_len )
#endif
/* %if-c-only */
-static void yynoreturn yy_fatal_error (yyconst char* msg )
+static void yynoreturn yy_fatal_error (const char* msg )
{
- (void) fprintf( stderr, "%s\n", msg );
+ fprintf( stderr, "%s\n", msg );
exit( YY_EXIT_FAILURE );
}
/* %endif */
@@ -2410,14 +2652,14 @@ static void yynoreturn yy_fatal_error (yyconst char* msg )
#define yyless(n) \
do \
{ \
- /* Undo effects of setting up evaltext. */ \
+ /* Undo effects of setting up yytext. */ \
int yyless_macro_arg = (n); \
YY_LESS_LINENO(yyless_macro_arg);\
- evaltext[evalleng] = (yy_hold_char); \
- (yy_c_buf_p) = evaltext + yyless_macro_arg; \
+ yytext[yyleng] = (yy_hold_char); \
+ (yy_c_buf_p) = yytext + yyless_macro_arg; \
(yy_hold_char) = *(yy_c_buf_p); \
*(yy_c_buf_p) = '\0'; \
- evalleng = yyless_macro_arg; \
+ yyleng = yyless_macro_arg; \
} \
while ( 0 )
@@ -2430,43 +2672,43 @@ static void yynoreturn yy_fatal_error (yyconst char* msg )
/** Get the current line number.
*
*/
-int evalget_lineno (void)
+int yyget_lineno (void)
{
-
- return evallineno;
+
+ return yylineno;
}
/** Get the input stream.
*
*/
-FILE *evalget_in (void)
+FILE *yyget_in (void)
{
- return evalin;
+ return yyin;
}
/** Get the output stream.
*
*/
-FILE *evalget_out (void)
+FILE *yyget_out (void)
{
- return evalout;
+ return yyout;
}
/** Get the length of the current token.
*
*/
-int evalget_leng (void)
+int yyget_leng (void)
{
- return evalleng;
+ return yyleng;
}
/** Get the current token.
*
*/
-char *evalget_text (void)
+char *yyget_text (void)
{
- return evaltext;
+ return yytext;
}
/* %if-reentrant */
@@ -2476,36 +2718,36 @@ char *evalget_text (void)
* @param _line_number line number
*
*/
-void evalset_lineno (int _line_number )
+void yyset_lineno (int _line_number )
{
- evallineno = _line_number;
+ yylineno = _line_number;
}
/** Set the input stream. This does not discard the current
* input buffer.
* @param _in_str A readable stream.
*
- * @see eval_switch_to_buffer
+ * @see yy_switch_to_buffer
*/
-void evalset_in (FILE * _in_str )
+void yyset_in (FILE * _in_str )
{
- evalin = _in_str ;
+ yyin = _in_str ;
}
-void evalset_out (FILE * _out_str )
+void yyset_out (FILE * _out_str )
{
- evalout = _out_str ;
+ yyout = _out_str ;
}
-int evalget_debug (void)
+int yyget_debug (void)
{
- return eval_flex_debug;
+ return yy_flex_debug;
}
-void evalset_debug (int _bdebug )
+void yyset_debug (int _bdebug )
{
- eval_flex_debug = _bdebug ;
+ yy_flex_debug = _bdebug ;
}
/* %endif */
@@ -2519,11 +2761,11 @@ void evalset_debug (int _bdebug )
static int yy_init_globals (void)
{
/* Initialization is the same as for the non-reentrant scanner.
- * This function is called from evallex_destroy(), so don't allocate here.
+ * This function is called from yylex_destroy(), so don't allocate here.
*/
- /* We do not touch evallineno unless the option is enabled. */
- evallineno = 1;
+ /* We do not touch yylineno unless the option is enabled. */
+ yylineno = 1;
(yy_buffer_stack) = NULL;
(yy_buffer_stack_top) = 0;
@@ -2539,41 +2781,41 @@ static int yy_init_globals (void)
/* Defined in main.c */
#ifdef YY_STDINIT
- evalin = stdin;
- evalout = stdout;
+ yyin = stdin;
+ yyout = stdout;
#else
- evalin = NULL;
- evalout = NULL;
+ yyin = NULL;
+ yyout = NULL;
#endif
/* For future reference: Set errno on error, since we are called by
- * evallex_init()
+ * yylex_init()
*/
return 0;
}
/* %endif */
/* %if-c-only SNIP! this currently causes conflicts with the c++ scanner */
-/* evallex_destroy is for both reentrant and non-reentrant scanners. */
-int evallex_destroy (void)
+/* yylex_destroy is for both reentrant and non-reentrant scanners. */
+int yylex_destroy (void)
{
/* Pop the buffer stack, destroying each element. */
while(YY_CURRENT_BUFFER){
- eval_delete_buffer(YY_CURRENT_BUFFER );
+ yy_delete_buffer( YY_CURRENT_BUFFER );
YY_CURRENT_BUFFER_LVALUE = NULL;
- evalpop_buffer_state();
+ yypop_buffer_state();
}
/* Destroy the stack itself. */
- evalfree((yy_buffer_stack) );
+ yyfree((yy_buffer_stack) );
(yy_buffer_stack) = NULL;
- evalfree ( (yy_state_buf) );
+ yyfree ( (yy_state_buf) );
(yy_state_buf) = NULL;
/* Reset the globals. This is important in a non-reentrant scanner so the next time
- * evallex() is called, initialization will occur. */
+ * yylex() is called, initialization will occur. */
yy_init_globals( );
/* %if-reentrant */
@@ -2587,7 +2829,7 @@ int evallex_destroy (void)
*/
#ifndef yytext_ptr
-static void yy_flex_strncpy (char* s1, yyconst char * s2, int n )
+static void yy_flex_strncpy (char* s1, const char * s2, int n )
{
int i;
@@ -2597,7 +2839,7 @@ static void yy_flex_strncpy (char* s1, yyconst char * s2, int n )
#endif
#ifdef YY_NEED_STRLEN
-static int yy_flex_strlen (yyconst char * s )
+static int yy_flex_strlen (const char * s )
{
int n;
for ( n = 0; s[n]; ++n )
@@ -2607,12 +2849,12 @@ static int yy_flex_strlen (yyconst char * s )
}
#endif
-void *evalalloc (yy_size_t size )
+void *yyalloc (yy_size_t size )
{
return malloc(size);
}
-void *evalrealloc (void * ptr, yy_size_t size )
+void *yyrealloc (void * ptr, yy_size_t size )
{
/* The cast to (char *) in the following accommodates both
@@ -2625,9 +2867,9 @@ void *evalrealloc (void * ptr, yy_size_t size )
return realloc(ptr, size);
}
-void evalfree (void * ptr )
+void yyfree (void * ptr )
{
- free( (char *) ptr ); /* see evalrealloc() for (char *) cast */
+ free( (char *) ptr ); /* see yyrealloc() for (char *) cast */
}
/* %if-tables-serialization definitions */
@@ -2637,22 +2879,24 @@ void evalfree (void * ptr )
/* %ok-for-header */
-#line 192 "lexer.ll"
-
+#line 212 "lexer.ll"
using namespace isc::eval;
void
-EvalContext::scanStringBegin()
+EvalContext::scanStringBegin(ParserType type)
{
+ start_token_flag = true;
+ start_token_value = type;
+
loc.initialize(&file_);
eval_flex_debug = trace_scanning_;
YY_BUFFER_STATE buffer;
buffer = eval_scan_bytes(string_.c_str(), string_.size());
if (!buffer) {
fatal("cannot scan string");
- // fatal() throws an exception so this can't be reached
+ /* fatal() throws an exception so this can't be reached */
}
}
@@ -2663,9 +2907,9 @@ EvalContext::scanStringEnd()
}
namespace {
-/// To avoid unused function error
+/** To avoid unused function error */
class Dummy {
- // cppcheck-suppress unusedPrivateFunction
+ /* cppcheck-suppress unusedPrivateFunction */
void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); }
};
}
diff --git a/src/lib/eval/lexer.ll b/src/lib/eval/lexer.ll
index 838123ad83..f49aa19543 100644
--- a/src/lib/eval/lexer.ll
+++ b/src/lib/eval/lexer.ll
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+/* Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -14,18 +14,25 @@
#include <asiolink/io_address.h>
#include <boost/lexical_cast.hpp>
-// Work around an incompatibility in flex (at least versions
-// 2.5.31 through 2.5.33): it generates code that does
-// not conform to C89. See Debian bug 333231
-// <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>.
+/* Please avoid C++ style comments (// ... eol) as they break flex 2.6.2 */
+
+/* Work around an incompatibility in flex (at least versions
+ 2.5.31 through 2.5.33): it generates code that does
+ not conform to C89. See Debian bug 333231
+ <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>. */
# undef yywrap
# define yywrap() 1
-// The location of the current token. The lexer will keep updating it. This
-// variable will be useful for logging errors.
+/* The location of the current token. The lexer will keep updating it. This
+ variable will be useful for logging errors. */
static isc::eval::location loc;
-// To avoid the call to exit... oops!
+namespace {
+ bool start_token_flag = false;
+ isc::eval::EvalContext::ParserType start_token_value;
+};
+
+/* To avoid the call to exit... oops! */
#define YY_FATAL_ERROR(msg) isc::eval::EvalContext::fatal(msg)
%}
@@ -66,34 +73,46 @@ addr4 [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+
addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]*
%{
-// This code run each time a pattern is matched. It updates the location
-// by moving it ahead by yyleng bytes. yyleng specifies the length of the
-// currently matched token.
+/* This code run each time a pattern is matched. It updates the location
+ by moving it ahead by yyleng bytes. yyleng specifies the length of the
+ currently matched token. */
#define YY_USER_ACTION loc.columns(evalleng);
%}
%%
%{
- // Code run each time evallex is called.
+ /* Code run each time evallex is called. */
loc.step();
+
+ if (start_token_flag) {
+ start_token_flag = false;
+ switch (start_token_value) {
+ case EvalContext::PARSER_BOOL:
+ return isc::eval::EvalParser::make_TOPLEVEL_BOOL(loc);
+ default:
+ case EvalContext::PARSER_STRING:
+ return isc::eval::EvalParser::make_TOPLEVEL_STRING(loc);
+ }
+ }
+
%}
{blank}+ {
- // Ok, we found a with space. Let's ignore it and update loc variable.
+ /* Ok, we found a with space. Let's ignore it and update loc variable. */
loc.step();
}
[\n]+ {
- // Newline found. Let's update the location and continue.
+ /* Newline found. Let's update the location and continue. */
loc.lines(evalleng);
loc.step();
}
\'[^\'\n]*\' {
- // A string has been matched. It contains the actual string and single quotes.
- // We need to get those quotes out of the way and just use its content, e.g.
- // for 'foo' we should get foo
+ /* A string has been matched. It contains the actual string and single quotes.
+ We need to get those quotes out of the way and just use its content, e.g.
+ for 'foo' we should get foo */
std::string tmp(evaltext+1);
tmp.resize(tmp.size() - 1);
@@ -101,41 +120,41 @@ addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]*
}
0[xX]{hex} {
- // A hex string has been matched. It contains the '0x' or '0X' header
- // followed by at least one hexadecimal digit.
+ /* A hex string has been matched. It contains the '0x' or '0X' header
+ followed by at least one hexadecimal digit. */
return isc::eval::EvalParser::make_HEXSTRING(evaltext, loc);
}
{int} {
- // An integer was found.
+ /* An integer was found. */
std::string tmp(evaltext);
try {
- // In substring we want to use negative values (e.g. -1).
- // In enterprise-id we need to use values up to 0xffffffff.
- // To cover both of those use cases, we need at least
- // int64_t.
+ /* In substring we want to use negative values (e.g. -1).
+ In enterprise-id we need to use values up to 0xffffffff.
+ To cover both of those use cases, we need at least
+ int64_t. */
static_cast<void>(boost::lexical_cast<int64_t>(tmp));
} catch (const boost::bad_lexical_cast &) {
driver.error(loc, "Failed to convert " + tmp + " to an integer.");
}
- // The parser needs the string form as double conversion is no lossless
+ /* The parser needs the string form as double conversion is no lossless */
return isc::eval::EvalParser::make_INTEGER(tmp, loc);
}
[A-Za-z]([-_A-Za-z0-9]*[A-Za-z0-9])?/({blank}|\n)*] {
- // This string specifies option name starting with a letter
- // and further containing letters, digits, hyphens and
- // underscores and finishing by letters or digits.
+ /* This string specifies option name starting with a letter
+ and further containing letters, digits, hyphens and
+ underscores and finishing by letters or digits. */
return isc::eval::EvalParser::make_OPTION_NAME(evaltext, loc);
}
{addr4}|{addr6} {
- // IPv4 or IPv6 address
+ /* IPv4 or IPv6 address */
std::string tmp(evaltext);
- // Some incorrect addresses can match so we have to check.
+ /* Some incorrect addresses can match so we have to check. */
try {
isc::asiolink::IOAddress ip(tmp);
} catch (...) {
@@ -194,15 +213,18 @@ addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]*
using namespace isc::eval;
void
-EvalContext::scanStringBegin()
+EvalContext::scanStringBegin(ParserType type)
{
+ start_token_flag = true;
+ start_token_value = type;
+
loc.initialize(&file_);
eval_flex_debug = trace_scanning_;
YY_BUFFER_STATE buffer;
buffer = eval_scan_bytes(string_.c_str(), string_.size());
if (!buffer) {
fatal("cannot scan string");
- // fatal() throws an exception so this can't be reached
+ /* fatal() throws an exception so this can't be reached */
}
}
@@ -213,9 +235,9 @@ EvalContext::scanStringEnd()
}
namespace {
-/// To avoid unused function error
+/** To avoid unused function error */
class Dummy {
- // cppcheck-suppress unusedPrivateFunction
+ /* cppcheck-suppress unusedPrivateFunction */
void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); }
};
}
diff --git a/src/lib/eval/location.hh b/src/lib/eval/location.hh
index a445cdf807..66556de21c 100644
--- a/src/lib/eval/location.hh
+++ b/src/lib/eval/location.hh
@@ -1,4 +1,4 @@
-// Generated 201611011345
+// Generated 201707051218
// A Bison parser, made by GNU Bison 3.0.4.
// Locations for Bison parsers in C++
diff --git a/src/lib/eval/parser.cc b/src/lib/eval/parser.cc
index 2891cb0b4e..f952ef31de 100644
--- a/src/lib/eval/parser.cc
+++ b/src/lib/eval/parser.cc
@@ -253,47 +253,47 @@ namespace isc { namespace eval {
{
switch (that.type_get ())
{
- case 56: // option_repr_type
+ case 59: // option_repr_type
value.move< TokenOption::RepresentationType > (that.value);
break;
- case 60: // pkt4_field
+ case 63: // pkt4_field
value.move< TokenPkt4::FieldType > (that.value);
break;
- case 61: // pkt6_field
+ case 64: // pkt6_field
value.move< TokenPkt6::FieldType > (that.value);
break;
- case 58: // pkt_metadata
+ case 61: // pkt_metadata
value.move< TokenPkt::MetadataType > (that.value);
break;
- case 62: // relay6_field
+ case 65: // relay6_field
value.move< TokenRelay6Field::FieldType > (that.value);
break;
- case 45: // "constant string"
- case 46: // "integer"
- case 47: // "constant hexstring"
- case 48: // "option name"
- case 49: // "ip address"
+ case 60: // nest_level
+ value.move< int8_t > (that.value);
+ break;
+
+ case 47: // "constant string"
+ case 48: // "integer"
+ case 49: // "constant hexstring"
+ case 50: // "option name"
+ case 51: // "ip address"
value.move< std::string > (that.value);
break;
- case 55: // option_code
+ case 58: // option_code
value.move< uint16_t > (that.value);
break;
- case 54: // integer_expr
- case 59: // enterprise_id
+ case 57: // integer_expr
+ case 62: // enterprise_id
value.move< uint32_t > (that.value);
break;
- case 57: // nest_level
- value.move< uint8_t > (that.value);
- break;
-
default:
break;
}
@@ -309,47 +309,47 @@ namespace isc { namespace eval {
state = that.state;
switch (that.type_get ())
{
- case 56: // option_repr_type
+ case 59: // option_repr_type
value.copy< TokenOption::RepresentationType > (that.value);
break;
- case 60: // pkt4_field
+ case 63: // pkt4_field
value.copy< TokenPkt4::FieldType > (that.value);
break;
- case 61: // pkt6_field
+ case 64: // pkt6_field
value.copy< TokenPkt6::FieldType > (that.value);
break;
- case 58: // pkt_metadata
+ case 61: // pkt_metadata
value.copy< TokenPkt::MetadataType > (that.value);
break;
- case 62: // relay6_field
+ case 65: // relay6_field
value.copy< TokenRelay6Field::FieldType > (that.value);
break;
- case 45: // "constant string"
- case 46: // "integer"
- case 47: // "constant hexstring"
- case 48: // "option name"
- case 49: // "ip address"
+ case 60: // nest_level
+ value.copy< int8_t > (that.value);
+ break;
+
+ case 47: // "constant string"
+ case 48: // "integer"
+ case 49: // "constant hexstring"
+ case 50: // "option name"
+ case 51: // "ip address"
value.copy< std::string > (that.value);
break;
- case 55: // option_code
+ case 58: // option_code
value.copy< uint16_t > (that.value);
break;
- case 54: // integer_expr
- case 59: // enterprise_id
+ case 57: // integer_expr
+ case 62: // enterprise_id
value.copy< uint32_t > (that.value);
break;
- case 57: // nest_level
- value.copy< uint8_t > (that.value);
- break;
-
default:
break;
}
@@ -386,100 +386,100 @@ namespace isc { namespace eval {
<< yysym.location << ": ";
switch (yytype)
{
- case 45: // "constant string"
+ case 47: // "constant string"
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< std::string > (); }
#line 394 "parser.cc" // lalr1.cc:636
break;
- case 46: // "integer"
+ case 48: // "integer"
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< std::string > (); }
#line 401 "parser.cc" // lalr1.cc:636
break;
- case 47: // "constant hexstring"
+ case 49: // "constant hexstring"
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< std::string > (); }
#line 408 "parser.cc" // lalr1.cc:636
break;
- case 48: // "option name"
+ case 50: // "option name"
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< std::string > (); }
#line 415 "parser.cc" // lalr1.cc:636
break;
- case 49: // "ip address"
+ case 51: // "ip address"
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< std::string > (); }
#line 422 "parser.cc" // lalr1.cc:636
break;
- case 54: // integer_expr
+ case 57: // integer_expr
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< uint32_t > (); }
#line 429 "parser.cc" // lalr1.cc:636
break;
- case 55: // option_code
+ case 58: // option_code
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< uint16_t > (); }
#line 436 "parser.cc" // lalr1.cc:636
break;
- case 56: // option_repr_type
+ case 59: // option_repr_type
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< TokenOption::RepresentationType > (); }
#line 443 "parser.cc" // lalr1.cc:636
break;
- case 57: // nest_level
+ case 60: // nest_level
-#line 105 "parser.yy" // lalr1.cc:636
- { yyoutput << yysym.value.template as< uint8_t > (); }
+#line 108 "parser.yy" // lalr1.cc:636
+ { yyoutput << yysym.value.template as< int8_t > (); }
#line 450 "parser.cc" // lalr1.cc:636
break;
- case 58: // pkt_metadata
+ case 61: // pkt_metadata
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< TokenPkt::MetadataType > (); }
#line 457 "parser.cc" // lalr1.cc:636
break;
- case 59: // enterprise_id
+ case 62: // enterprise_id
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< uint32_t > (); }
#line 464 "parser.cc" // lalr1.cc:636
break;
- case 60: // pkt4_field
+ case 63: // pkt4_field
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< TokenPkt4::FieldType > (); }
#line 471 "parser.cc" // lalr1.cc:636
break;
- case 61: // pkt6_field
+ case 64: // pkt6_field
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< TokenPkt6::FieldType > (); }
#line 478 "parser.cc" // lalr1.cc:636
break;
- case 62: // relay6_field
+ case 65: // relay6_field
-#line 105 "parser.yy" // lalr1.cc:636
+#line 108 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< TokenRelay6Field::FieldType > (); }
#line 485 "parser.cc" // lalr1.cc:636
break;
@@ -586,7 +586,7 @@ namespace isc { namespace eval {
/// The return value of parse ().
int yyresult;
- // FIXME: This shoud be completely indented. It is not yet to
+ // FIXME: This should be completely indented. It is not yet to
// avoid gratuitous conflicts when merging into the master branch.
try
{
@@ -681,47 +681,47 @@ namespace isc { namespace eval {
when using variants. */
switch (yyr1_[yyn])
{
- case 56: // option_repr_type
+ case 59: // option_repr_type
yylhs.value.build< TokenOption::RepresentationType > ();
break;
- case 60: // pkt4_field
+ case 63: // pkt4_field
yylhs.value.build< TokenPkt4::FieldType > ();
break;
- case 61: // pkt6_field
+ case 64: // pkt6_field
yylhs.value.build< TokenPkt6::FieldType > ();
break;
- case 58: // pkt_metadata
+ case 61: // pkt_metadata
yylhs.value.build< TokenPkt::MetadataType > ();
break;
- case 62: // relay6_field
+ case 65: // relay6_field
yylhs.value.build< TokenRelay6Field::FieldType > ();
break;
- case 45: // "constant string"
- case 46: // "integer"
- case 47: // "constant hexstring"
- case 48: // "option name"
- case 49: // "ip address"
+ case 60: // nest_level
+ yylhs.value.build< int8_t > ();
+ break;
+
+ case 47: // "constant string"
+ case 48: // "integer"
+ case 49: // "constant hexstring"
+ case 50: // "option name"
+ case 51: // "ip address"
yylhs.value.build< std::string > ();
break;
- case 55: // option_code
+ case 58: // option_code
yylhs.value.build< uint16_t > ();
break;
- case 54: // integer_expr
- case 59: // enterprise_id
+ case 57: // integer_expr
+ case 62: // enterprise_id
yylhs.value.build< uint32_t > ();
break;
- case 57: // nest_level
- yylhs.value.build< uint8_t > ();
- break;
-
default:
break;
}
@@ -739,8 +739,8 @@ namespace isc { namespace eval {
{
switch (yyn)
{
- case 4:
-#line 119 "parser.yy" // lalr1.cc:859
+ case 6:
+#line 128 "parser.yy" // lalr1.cc:859
{
TokenPtr neg(new TokenNot());
ctx.expression.push_back(neg);
@@ -748,8 +748,8 @@ namespace isc { namespace eval {
#line 749 "parser.cc" // lalr1.cc:859
break;
- case 5:
-#line 124 "parser.yy" // lalr1.cc:859
+ case 7:
+#line 133 "parser.yy" // lalr1.cc:859
{
TokenPtr neg(new TokenAnd());
ctx.expression.push_back(neg);
@@ -757,8 +757,8 @@ namespace isc { namespace eval {
#line 758 "parser.cc" // lalr1.cc:859
break;
- case 6:
-#line 129 "parser.yy" // lalr1.cc:859
+ case 8:
+#line 138 "parser.yy" // lalr1.cc:859
{
TokenPtr neg(new TokenOr());
ctx.expression.push_back(neg);
@@ -766,8 +766,8 @@ namespace isc { namespace eval {
#line 767 "parser.cc" // lalr1.cc:859
break;
- case 7:
-#line 134 "parser.yy" // lalr1.cc:859
+ case 9:
+#line 143 "parser.yy" // lalr1.cc:859
{
TokenPtr eq(new TokenEqual());
ctx.expression.push_back(eq);
@@ -775,8 +775,8 @@ namespace isc { namespace eval {
#line 776 "parser.cc" // lalr1.cc:859
break;
- case 8:
-#line 139 "parser.yy" // lalr1.cc:859
+ case 10:
+#line 148 "parser.yy" // lalr1.cc:859
{
TokenPtr opt(new TokenOption(yystack_[3].value.as< uint16_t > (), TokenOption::EXISTS));
ctx.expression.push_back(opt);
@@ -784,8 +784,8 @@ namespace isc { namespace eval {
#line 785 "parser.cc" // lalr1.cc:859
break;
- case 9:
-#line 144 "parser.yy" // lalr1.cc:859
+ case 11:
+#line 153 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V4:
@@ -808,13 +808,13 @@ namespace isc { namespace eval {
#line 809 "parser.cc" // lalr1.cc:859
break;
- case 10:
-#line 164 "parser.yy" // lalr1.cc:859
+ case 12:
+#line 173 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V6:
{
- TokenPtr opt(new TokenRelay6Option(yystack_[8].value.as< uint8_t > (), yystack_[3].value.as< uint16_t > (), TokenOption::EXISTS));
+ TokenPtr opt(new TokenRelay6Option(yystack_[8].value.as< int8_t > (), yystack_[3].value.as< uint16_t > (), TokenOption::EXISTS));
ctx.expression.push_back(opt);
break;
}
@@ -826,8 +826,8 @@ namespace isc { namespace eval {
#line 827 "parser.cc" // lalr1.cc:859
break;
- case 11:
-#line 178 "parser.yy" // lalr1.cc:859
+ case 13:
+#line 187 "parser.yy" // lalr1.cc:859
{
// Expression: vendor-class[1234].exists
//
@@ -839,8 +839,8 @@ namespace isc { namespace eval {
#line 840 "parser.cc" // lalr1.cc:859
break;
- case 12:
-#line 187 "parser.yy" // lalr1.cc:859
+ case 14:
+#line 196 "parser.yy" // lalr1.cc:859
{
// Expression: vendor[1234].exists
//
@@ -852,8 +852,8 @@ namespace isc { namespace eval {
#line 853 "parser.cc" // lalr1.cc:859
break;
- case 13:
-#line 196 "parser.yy" // lalr1.cc:859
+ case 15:
+#line 205 "parser.yy" // lalr1.cc:859
{
// Expression vendor[1234].option[123].exists
//
@@ -866,8 +866,8 @@ namespace isc { namespace eval {
#line 867 "parser.cc" // lalr1.cc:859
break;
- case 14:
-#line 208 "parser.yy" // lalr1.cc:859
+ case 16:
+#line 217 "parser.yy" // lalr1.cc:859
{
TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ()));
ctx.expression.push_back(str);
@@ -875,8 +875,8 @@ namespace isc { namespace eval {
#line 876 "parser.cc" // lalr1.cc:859
break;
- case 15:
-#line 213 "parser.yy" // lalr1.cc:859
+ case 17:
+#line 222 "parser.yy" // lalr1.cc:859
{
TokenPtr hex(new TokenHexString(yystack_[0].value.as< std::string > ()));
ctx.expression.push_back(hex);
@@ -884,8 +884,8 @@ namespace isc { namespace eval {
#line 885 "parser.cc" // lalr1.cc:859
break;
- case 16:
-#line 218 "parser.yy" // lalr1.cc:859
+ case 18:
+#line 227 "parser.yy" // lalr1.cc:859
{
TokenPtr ip(new TokenIpAddress(yystack_[0].value.as< std::string > ()));
ctx.expression.push_back(ip);
@@ -893,8 +893,8 @@ namespace isc { namespace eval {
#line 894 "parser.cc" // lalr1.cc:859
break;
- case 17:
-#line 223 "parser.yy" // lalr1.cc:859
+ case 19:
+#line 232 "parser.yy" // lalr1.cc:859
{
TokenPtr opt(new TokenOption(yystack_[3].value.as< uint16_t > (), yystack_[0].value.as< TokenOption::RepresentationType > ()));
ctx.expression.push_back(opt);
@@ -902,8 +902,8 @@ namespace isc { namespace eval {
#line 903 "parser.cc" // lalr1.cc:859
break;
- case 18:
-#line 228 "parser.yy" // lalr1.cc:859
+ case 20:
+#line 237 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V4:
@@ -926,13 +926,13 @@ namespace isc { namespace eval {
#line 927 "parser.cc" // lalr1.cc:859
break;
- case 19:
-#line 249 "parser.yy" // lalr1.cc:859
+ case 21:
+#line 258 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V6:
{
- TokenPtr opt(new TokenRelay6Option(yystack_[8].value.as< uint8_t > (), yystack_[3].value.as< uint16_t > (), yystack_[0].value.as< TokenOption::RepresentationType > ()));
+ TokenPtr opt(new TokenRelay6Option(yystack_[8].value.as< int8_t > (), yystack_[3].value.as< uint16_t > (), yystack_[0].value.as< TokenOption::RepresentationType > ()));
ctx.expression.push_back(opt);
break;
}
@@ -944,8 +944,8 @@ namespace isc { namespace eval {
#line 945 "parser.cc" // lalr1.cc:859
break;
- case 20:
-#line 264 "parser.yy" // lalr1.cc:859
+ case 22:
+#line 273 "parser.yy" // lalr1.cc:859
{
TokenPtr pkt_metadata(new TokenPkt(yystack_[0].value.as< TokenPkt::MetadataType > ()));
ctx.expression.push_back(pkt_metadata);
@@ -953,8 +953,8 @@ namespace isc { namespace eval {
#line 954 "parser.cc" // lalr1.cc:859
break;
- case 21:
-#line 269 "parser.yy" // lalr1.cc:859
+ case 23:
+#line 278 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V4:
@@ -971,8 +971,8 @@ namespace isc { namespace eval {
#line 972 "parser.cc" // lalr1.cc:859
break;
- case 22:
-#line 283 "parser.yy" // lalr1.cc:859
+ case 24:
+#line 292 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V6:
@@ -989,13 +989,13 @@ namespace isc { namespace eval {
#line 990 "parser.cc" // lalr1.cc:859
break;
- case 23:
-#line 297 "parser.yy" // lalr1.cc:859
+ case 25:
+#line 306 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V6:
{
- TokenPtr relay6field(new TokenRelay6Field(yystack_[3].value.as< uint8_t > (), yystack_[0].value.as< TokenRelay6Field::FieldType > ()));
+ TokenPtr relay6field(new TokenRelay6Field(yystack_[3].value.as< int8_t > (), yystack_[0].value.as< TokenRelay6Field::FieldType > ()));
ctx.expression.push_back(relay6field);
break;
}
@@ -1007,8 +1007,8 @@ namespace isc { namespace eval {
#line 1008 "parser.cc" // lalr1.cc:859
break;
- case 24:
-#line 312 "parser.yy" // lalr1.cc:859
+ case 26:
+#line 321 "parser.yy" // lalr1.cc:859
{
TokenPtr sub(new TokenSubstring());
ctx.expression.push_back(sub);
@@ -1016,8 +1016,8 @@ namespace isc { namespace eval {
#line 1017 "parser.cc" // lalr1.cc:859
break;
- case 25:
-#line 317 "parser.yy" // lalr1.cc:859
+ case 27:
+#line 326 "parser.yy" // lalr1.cc:859
{
TokenPtr conc(new TokenConcat());
ctx.expression.push_back(conc);
@@ -1025,8 +1025,8 @@ namespace isc { namespace eval {
#line 1026 "parser.cc" // lalr1.cc:859
break;
- case 26:
-#line 322 "parser.yy" // lalr1.cc:859
+ case 28:
+#line 331 "parser.yy" // lalr1.cc:859
{
// expression: vendor.enterprise
//
@@ -1038,8 +1038,8 @@ namespace isc { namespace eval {
#line 1039 "parser.cc" // lalr1.cc:859
break;
- case 27:
-#line 331 "parser.yy" // lalr1.cc:859
+ case 29:
+#line 340 "parser.yy" // lalr1.cc:859
{
// expression: vendor-class.enterprise
//
@@ -1052,8 +1052,8 @@ namespace isc { namespace eval {
#line 1053 "parser.cc" // lalr1.cc:859
break;
- case 28:
-#line 341 "parser.yy" // lalr1.cc:859
+ case 30:
+#line 350 "parser.yy" // lalr1.cc:859
{
// This token will search for vendor option with
// specified enterprise-id. If found, will search
@@ -1065,8 +1065,8 @@ namespace isc { namespace eval {
#line 1066 "parser.cc" // lalr1.cc:859
break;
- case 29:
-#line 350 "parser.yy" // lalr1.cc:859
+ case 31:
+#line 359 "parser.yy" // lalr1.cc:859
{
// expression: vendor-class[1234].data
//
@@ -1082,8 +1082,8 @@ namespace isc { namespace eval {
#line 1083 "parser.cc" // lalr1.cc:859
break;
- case 30:
-#line 363 "parser.yy" // lalr1.cc:859
+ case 32:
+#line 372 "parser.yy" // lalr1.cc:859
{
// expression: vendor-class[1234].data[5]
//
@@ -1099,8 +1099,8 @@ namespace isc { namespace eval {
#line 1100 "parser.cc" // lalr1.cc:859
break;
- case 31:
-#line 376 "parser.yy" // lalr1.cc:859
+ case 33:
+#line 385 "parser.yy" // lalr1.cc:859
{
TokenPtr integer(new TokenInteger(yystack_[0].value.as< uint32_t > ()));
ctx.expression.push_back(integer);
@@ -1108,208 +1108,208 @@ namespace isc { namespace eval {
#line 1109 "parser.cc" // lalr1.cc:859
break;
- case 32:
-#line 383 "parser.yy" // lalr1.cc:859
+ case 34:
+#line 392 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< uint32_t > () = ctx.convertUint32(yystack_[0].value.as< std::string > (), yystack_[0].location);
}
#line 1117 "parser.cc" // lalr1.cc:859
break;
- case 33:
-#line 389 "parser.yy" // lalr1.cc:859
+ case 35:
+#line 398 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< uint16_t > () = ctx.convertOptionCode(yystack_[0].value.as< std::string > (), yystack_[0].location);
}
#line 1125 "parser.cc" // lalr1.cc:859
break;
- case 34:
-#line 393 "parser.yy" // lalr1.cc:859
+ case 36:
+#line 402 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< uint16_t > () = ctx.convertOptionName(yystack_[0].value.as< std::string > (), yystack_[0].location);
}
#line 1133 "parser.cc" // lalr1.cc:859
break;
- case 35:
-#line 399 "parser.yy" // lalr1.cc:859
+ case 37:
+#line 408 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenOption::RepresentationType > () = TokenOption::TEXTUAL;
}
#line 1141 "parser.cc" // lalr1.cc:859
break;
- case 36:
-#line 403 "parser.yy" // lalr1.cc:859
+ case 38:
+#line 412 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenOption::RepresentationType > () = TokenOption::HEXADECIMAL;
}
#line 1149 "parser.cc" // lalr1.cc:859
break;
- case 37:
-#line 409 "parser.yy" // lalr1.cc:859
+ case 39:
+#line 418 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< uint8_t > () = ctx.convertNestLevelNumber(yystack_[0].value.as< std::string > (), yystack_[0].location);
+ yylhs.value.as< int8_t > () = ctx.convertNestLevelNumber(yystack_[0].value.as< std::string > (), yystack_[0].location);
}
#line 1157 "parser.cc" // lalr1.cc:859
break;
- case 38:
-#line 418 "parser.yy" // lalr1.cc:859
+ case 40:
+#line 427 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::IFACE;
}
#line 1165 "parser.cc" // lalr1.cc:859
break;
- case 39:
-#line 422 "parser.yy" // lalr1.cc:859
+ case 41:
+#line 431 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::SRC;
}
#line 1173 "parser.cc" // lalr1.cc:859
break;
- case 40:
-#line 426 "parser.yy" // lalr1.cc:859
+ case 42:
+#line 435 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::DST;
}
#line 1181 "parser.cc" // lalr1.cc:859
break;
- case 41:
-#line 430 "parser.yy" // lalr1.cc:859
+ case 43:
+#line 439 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::LEN;
}
#line 1189 "parser.cc" // lalr1.cc:859
break;
- case 42:
-#line 436 "parser.yy" // lalr1.cc:859
+ case 44:
+#line 445 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< uint32_t > () = ctx.convertUint32(yystack_[0].value.as< std::string > (), yystack_[0].location);
}
#line 1197 "parser.cc" // lalr1.cc:859
break;
- case 43:
-#line 440 "parser.yy" // lalr1.cc:859
+ case 45:
+#line 449 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< uint32_t > () = 0;
}
#line 1205 "parser.cc" // lalr1.cc:859
break;
- case 44:
-#line 446 "parser.yy" // lalr1.cc:859
+ case 46:
+#line 455 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::CHADDR;
}
#line 1213 "parser.cc" // lalr1.cc:859
break;
- case 45:
-#line 450 "parser.yy" // lalr1.cc:859
+ case 47:
+#line 459 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::HLEN;
}
#line 1221 "parser.cc" // lalr1.cc:859
break;
- case 46:
-#line 454 "parser.yy" // lalr1.cc:859
+ case 48:
+#line 463 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::HTYPE;
}
#line 1229 "parser.cc" // lalr1.cc:859
break;
- case 47:
-#line 458 "parser.yy" // lalr1.cc:859
+ case 49:
+#line 467 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::CIADDR;
}
#line 1237 "parser.cc" // lalr1.cc:859
break;
- case 48:
-#line 462 "parser.yy" // lalr1.cc:859
+ case 50:
+#line 471 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::GIADDR;
}
#line 1245 "parser.cc" // lalr1.cc:859
break;
- case 49:
-#line 466 "parser.yy" // lalr1.cc:859
+ case 51:
+#line 475 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::YIADDR;
}
#line 1253 "parser.cc" // lalr1.cc:859
break;
- case 50:
-#line 470 "parser.yy" // lalr1.cc:859
+ case 52:
+#line 479 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::SIADDR;
}
#line 1261 "parser.cc" // lalr1.cc:859
break;
- case 51:
-#line 474 "parser.yy" // lalr1.cc:859
+ case 53:
+#line 483 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::MSGTYPE;
}
#line 1269 "parser.cc" // lalr1.cc:859
break;
- case 52:
-#line 478 "parser.yy" // lalr1.cc:859
+ case 54:
+#line 487 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::TRANSID;
}
#line 1277 "parser.cc" // lalr1.cc:859
break;
- case 53:
-#line 484 "parser.yy" // lalr1.cc:859
+ case 55:
+#line 493 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt6::FieldType > () = TokenPkt6::MSGTYPE;
}
#line 1285 "parser.cc" // lalr1.cc:859
break;
- case 54:
-#line 488 "parser.yy" // lalr1.cc:859
+ case 56:
+#line 497 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenPkt6::FieldType > () = TokenPkt6::TRANSID;
}
#line 1293 "parser.cc" // lalr1.cc:859
break;
- case 55:
-#line 494 "parser.yy" // lalr1.cc:859
+ case 57:
+#line 503 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenRelay6Field::FieldType > () = TokenRelay6Field::PEERADDR;
}
#line 1301 "parser.cc" // lalr1.cc:859
break;
- case 56:
-#line 498 "parser.yy" // lalr1.cc:859
+ case 58:
+#line 507 "parser.yy" // lalr1.cc:859
{
yylhs.value.as< TokenRelay6Field::FieldType > () = TokenRelay6Field::LINKADDR;
}
#line 1309 "parser.cc" // lalr1.cc:859
break;
- case 57:
-#line 504 "parser.yy" // lalr1.cc:859
+ case 59:
+#line 513 "parser.yy" // lalr1.cc:859
{
TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ()));
ctx.expression.push_back(str);
@@ -1317,8 +1317,8 @@ namespace isc { namespace eval {
#line 1318 "parser.cc" // lalr1.cc:859
break;
- case 58:
-#line 511 "parser.yy" // lalr1.cc:859
+ case 60:
+#line 520 "parser.yy" // lalr1.cc:859
{
TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ()));
ctx.expression.push_back(str);
@@ -1326,8 +1326,8 @@ namespace isc { namespace eval {
#line 1327 "parser.cc" // lalr1.cc:859
break;
- case 59:
-#line 516 "parser.yy" // lalr1.cc:859
+ case 61:
+#line 525 "parser.yy" // lalr1.cc:859
{
TokenPtr str(new TokenString("all"));
ctx.expression.push_back(str);
@@ -1591,153 +1591,162 @@ namespace isc { namespace eval {
}
- const signed char EvalParser::yypact_ninf_ = -91;
+ const signed char EvalParser::yypact_ninf_ = -106;
const signed char EvalParser::yytable_ninf_ = -1;
const short int
EvalParser::yypact_[] =
{
- 3, 3, 3, -5, 13, 23, 4, 14, 54, 80,
- 68, 37, 95, -91, -91, -91, -91, 93, 35, 98,
- -91, 73, -91, 67, 67, 64, 50, 69, 45, 45,
- 84, -20, 74, -20, 81, -91, 3, 3, 45, -91,
- -91, -91, 109, 111, -91, 112, -91, -91, -91, -91,
- -91, -91, -91, -91, -91, -91, -91, -91, -91, -91,
- -91, 114, 115, 116, 100, 103, 96, 97, -91, -91,
- -91, -91, -91, 118, -91, 119, -91, -91, 129, -91,
- 120, 121, 122, 67, 67, 64, -20, -20, 94, 45,
- 123, 125, 16, 28, 6, 127, 128, 130, 131, 132,
- -91, 113, 140, -14, -2, -91, -91, -91, -91, -91,
- -91, 135, -91, -91, -91, 134, 136, 137, 138, 139,
- -30, -91, -91, 142, 143, -91, 67, 58, 58, 12,
- 108, 149, -91, -91, 155, 117, 67, 145, 147, 148,
- -91, 150, 151, 152, 67, 67, -91, 153, 70, 156,
- 157, 86, -91, -91, 154, 158, -91, -91, 58, 58
+ 24, 8, 51, 5, 8, 8, -2, 10, 20, -7,
+ 31, 47, 72, 42, 69, 90, -106, -106, -106, -106,
+ -106, 83, 78, -106, 25, 82, 108, 91, 104, -106,
+ -106, 36, -106, 71, 71, 53, 44, 85, 51, 51,
+ 87, -17, 86, -17, 88, 8, 8, 51, 71, 71,
+ 53, -17, -17, -106, -106, -106, 114, 116, -106, 118,
+ -106, -106, -106, -106, -106, -106, -106, -106, -106, -106,
+ -106, -106, -106, -106, -106, 99, 100, -106, -106, -106,
+ -106, -106, 121, -106, 122, -106, -106, 132, -106, 124,
+ 125, 126, 127, 128, 129, 130, 131, 96, 51, 133,
+ 134, 135, 136, 137, 138, 139, 55, 60, -5, -106,
+ 113, 152, -16, 7, 110, 110, 23, 115, 148, -106,
+ -106, -106, -106, -106, -106, 145, -106, -106, -106, -28,
+ -106, -106, 146, 147, -106, 149, 150, 71, -106, -106,
+ 158, 117, 71, 71, 71, 151, -106, 153, 154, 155,
+ 156, 157, -106, 159, 160, 161, 63, 76, 110, 110,
+ -106, -106, -106, -106
};
const unsigned char
EvalParser::yydefact_[] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 14, 32, 15, 16, 0, 2, 0,
- 31, 0, 4, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 0, 0, 0, 3,
- 33, 34, 0, 0, 37, 0, 38, 39, 40, 41,
- 20, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- 21, 0, 0, 0, 0, 0, 0, 0, 53, 54,
- 22, 43, 42, 0, 27, 0, 26, 5, 6, 7,
+ 0, 0, 0, 0, 0, 0, 16, 34, 17, 18,
+ 2, 4, 0, 33, 0, 0, 0, 0, 0, 3,
+ 1, 0, 6, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 5, 35, 36, 0, 0, 39, 0,
+ 40, 41, 42, 43, 22, 46, 47, 48, 49, 50,
+ 51, 52, 53, 54, 23, 0, 0, 55, 56, 24,
+ 45, 44, 0, 29, 0, 28, 7, 8, 9, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 57, 0, 0, 0, 0, 35, 36, 8, 17, 9,
- 18, 0, 55, 56, 23, 0, 0, 0, 0, 0,
- 0, 25, 11, 29, 0, 12, 0, 0, 0, 0,
- 0, 0, 59, 58, 0, 0, 0, 0, 0, 0,
- 24, 0, 0, 0, 0, 0, 30, 0, 0, 0,
- 0, 0, 10, 19, 0, 0, 13, 28, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 59,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 37,
+ 38, 10, 19, 11, 20, 0, 57, 58, 25, 0,
+ 27, 13, 31, 0, 14, 0, 0, 0, 61, 60,
+ 0, 0, 0, 0, 0, 0, 26, 0, 0, 0,
+ 0, 0, 32, 0, 0, 0, 0, 0, 0, 0,
+ 12, 21, 15, 30
};
- const signed char
+ const short int
EvalParser::yypgoto_[] =
{
- -91, -91, 30, -27, -91, -24, -90, 79, -91, -23,
- -91, -91, -91, -91, -91
+ -106, -106, -106, 18, -1, -106, -34, -105, 140, -106,
+ -22, -106, -106, -106, -106, -106
};
const short int
EvalParser::yydefgoto_[] =
{
- -1, 17, 18, 19, 20, 42, 108, 45, 50, 73,
- 60, 70, 114, 101, 134
+ -1, 3, 20, 21, 22, 23, 56, 122, 59, 64,
+ 82, 74, 79, 128, 110, 140
};
const unsigned char
EvalParser::yytable_[] =
{
- 43, 66, 67, 110, 132, 122, 1, 124, 2, 23,
- 75, 79, 3, 4, 5, 111, 133, 125, 112, 113,
- 26, 138, 71, 6, 112, 113, 72, 24, 7, 123,
- 27, 21, 22, 105, 106, 107, 8, 25, 110, 9,
- 10, 36, 37, 11, 12, 105, 106, 109, 13, 14,
- 15, 31, 16, 32, 61, 62, 63, 28, 153, 95,
- 96, 157, 102, 98, 99, 6, 77, 78, 153, 157,
- 7, 46, 47, 48, 49, 105, 106, 39, 8, 36,
- 37, 9, 10, 29, 30, 64, 65, 105, 106, 152,
- 13, 14, 15, 35, 16, 51, 52, 53, 54, 55,
- 56, 57, 137, 105, 106, 156, 38, 58, 59, 33,
- 44, 34, 142, 40, 86, 41, 32, 87, 74, 34,
- 149, 150, 68, 69, 80, 76, 81, 82, 83, 84,
- 85, 88, 89, 90, 91, 36, 92, 93, 94, 103,
- 100, 104, 115, 116, 121, 117, 118, 119, 120, 126,
- 127, 123, 128, 129, 130, 131, 135, 136, 139, 140,
- 143, 144, 145, 141, 97, 146, 147, 0, 148, 151,
- 158, 154, 155, 0, 159
+ 57, 29, 124, 131, 125, 30, 138, 126, 127, 36,
+ 124, 4, 33, 5, 89, 90, 133, 6, 7, 8,
+ 139, 84, 31, 32, 34, 80, 134, 132, 9, 92,
+ 93, 81, 135, 10, 35, 126, 127, 75, 76, 48,
+ 53, 11, 45, 46, 12, 13, 88, 37, 14, 15,
+ 38, 161, 163, 161, 163, 16, 17, 18, 40, 19,
+ 24, 25, 26, 86, 87, 60, 61, 62, 63, 1,
+ 2, 9, 119, 120, 121, 39, 10, 119, 120, 123,
+ 119, 120, 160, 41, 11, 42, 47, 12, 13, 45,
+ 46, 27, 28, 119, 120, 162, 49, 111, 16, 17,
+ 18, 58, 19, 145, 43, 51, 44, 42, 148, 149,
+ 150, 65, 66, 67, 68, 69, 70, 71, 52, 54,
+ 44, 55, 50, 72, 73, 77, 78, 119, 120, 94,
+ 83, 95, 85, 96, 97, 98, 99, 100, 45, 101,
+ 102, 103, 104, 105, 109, 106, 107, 108, 129, 112,
+ 113, 114, 115, 116, 117, 118, 130, 136, 132, 137,
+ 141, 142, 146, 143, 144, 147, 151, 0, 152, 153,
+ 154, 155, 0, 156, 0, 157, 158, 159, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 91
};
const short int
EvalParser::yycheck_[] =
{
- 24, 28, 29, 93, 34, 19, 3, 9, 5, 14,
- 33, 38, 9, 10, 11, 9, 46, 19, 12, 13,
- 16, 9, 42, 20, 12, 13, 46, 14, 25, 43,
- 16, 1, 2, 17, 18, 19, 33, 14, 128, 36,
- 37, 6, 7, 40, 41, 17, 18, 19, 45, 46,
- 47, 14, 49, 16, 9, 10, 11, 3, 148, 83,
- 84, 151, 89, 86, 87, 20, 36, 37, 158, 159,
- 25, 21, 22, 23, 24, 17, 18, 4, 33, 6,
- 7, 36, 37, 3, 16, 40, 41, 17, 18, 19,
- 45, 46, 47, 0, 49, 26, 27, 28, 29, 30,
- 31, 32, 126, 17, 18, 19, 8, 38, 39, 14,
- 46, 16, 136, 46, 14, 48, 16, 14, 44, 16,
- 144, 145, 38, 39, 15, 44, 15, 15, 14, 14,
- 14, 35, 35, 15, 15, 6, 16, 16, 16, 16,
- 46, 16, 15, 15, 4, 15, 15, 15, 35, 14,
- 16, 43, 16, 16, 16, 16, 14, 14, 9, 4,
- 15, 14, 14, 46, 85, 15, 15, -1, 16, 16,
- 16, 15, 15, -1, 16
+ 34, 2, 107, 19, 9, 0, 34, 12, 13, 16,
+ 115, 3, 14, 5, 48, 49, 9, 9, 10, 11,
+ 48, 43, 4, 5, 14, 42, 19, 43, 20, 51,
+ 52, 48, 9, 25, 14, 12, 13, 38, 39, 14,
+ 4, 33, 6, 7, 36, 37, 47, 16, 40, 41,
+ 3, 156, 157, 158, 159, 47, 48, 49, 16, 51,
+ 9, 10, 11, 45, 46, 21, 22, 23, 24, 45,
+ 46, 20, 17, 18, 19, 3, 25, 17, 18, 19,
+ 17, 18, 19, 14, 33, 16, 8, 36, 37, 6,
+ 7, 40, 41, 17, 18, 19, 14, 98, 47, 48,
+ 49, 48, 51, 137, 14, 14, 16, 16, 142, 143,
+ 144, 26, 27, 28, 29, 30, 31, 32, 14, 48,
+ 16, 50, 14, 38, 39, 38, 39, 17, 18, 15,
+ 44, 15, 44, 15, 35, 35, 15, 15, 6, 15,
+ 15, 15, 15, 15, 48, 16, 16, 16, 35, 16,
+ 16, 16, 16, 16, 16, 16, 4, 9, 43, 14,
+ 14, 14, 4, 14, 14, 48, 15, -1, 15, 15,
+ 15, 15, -1, 16, -1, 16, 16, 16, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 50
};
const unsigned char
EvalParser::yystos_[] =
{
- 0, 3, 5, 9, 10, 11, 20, 25, 33, 36,
- 37, 40, 41, 45, 46, 47, 49, 51, 52, 53,
- 54, 52, 52, 14, 14, 14, 16, 16, 3, 3,
- 16, 14, 16, 14, 16, 0, 6, 7, 8, 4,
- 46, 48, 55, 55, 46, 57, 21, 22, 23, 24,
- 58, 26, 27, 28, 29, 30, 31, 32, 38, 39,
- 60, 9, 10, 11, 40, 41, 53, 53, 38, 39,
- 61, 42, 46, 59, 44, 59, 44, 52, 52, 53,
- 15, 15, 15, 14, 14, 14, 14, 14, 35, 35,
- 15, 15, 16, 16, 16, 55, 55, 57, 59, 59,
- 46, 63, 53, 16, 16, 17, 18, 19, 56, 19,
- 56, 9, 12, 13, 62, 15, 15, 15, 15, 15,
- 35, 4, 19, 43, 9, 19, 14, 16, 16, 16,
- 16, 16, 34, 46, 64, 14, 14, 55, 9, 9,
- 4, 46, 55, 15, 14, 14, 15, 15, 16, 55,
- 55, 16, 19, 56, 15, 15, 19, 56, 16, 16
+ 0, 45, 46, 53, 3, 5, 9, 10, 11, 20,
+ 25, 33, 36, 37, 40, 41, 47, 48, 49, 51,
+ 54, 55, 56, 57, 9, 10, 11, 40, 41, 56,
+ 0, 55, 55, 14, 14, 14, 16, 16, 3, 3,
+ 16, 14, 16, 14, 16, 6, 7, 8, 14, 14,
+ 14, 14, 14, 4, 48, 50, 58, 58, 48, 60,
+ 21, 22, 23, 24, 61, 26, 27, 28, 29, 30,
+ 31, 32, 38, 39, 63, 56, 56, 38, 39, 64,
+ 42, 48, 62, 44, 62, 44, 55, 55, 56, 58,
+ 58, 60, 62, 62, 15, 15, 15, 35, 35, 15,
+ 15, 15, 15, 15, 15, 15, 16, 16, 16, 48,
+ 66, 56, 16, 16, 16, 16, 16, 16, 16, 17,
+ 18, 19, 59, 19, 59, 9, 12, 13, 65, 35,
+ 4, 19, 43, 9, 19, 9, 9, 14, 34, 48,
+ 67, 14, 14, 14, 14, 58, 4, 48, 58, 58,
+ 58, 15, 15, 15, 15, 15, 16, 16, 16, 16,
+ 19, 59, 19, 59
};
const unsigned char
EvalParser::yyr1_[] =
{
- 0, 50, 51, 52, 52, 52, 52, 52, 52, 52,
- 52, 52, 52, 52, 53, 53, 53, 53, 53, 53,
- 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
- 53, 53, 54, 55, 55, 56, 56, 57, 58, 58,
- 58, 58, 59, 59, 60, 60, 60, 60, 60, 60,
- 60, 60, 60, 61, 61, 62, 62, 63, 64, 64
+ 0, 52, 53, 53, 54, 55, 55, 55, 55, 55,
+ 55, 55, 55, 55, 55, 55, 56, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+ 56, 56, 56, 56, 57, 58, 58, 59, 59, 60,
+ 61, 61, 61, 61, 62, 62, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 64, 64, 65, 65, 66,
+ 67, 67
};
const unsigned char
EvalParser::yyr2_[] =
{
- 0, 2, 1, 3, 2, 3, 3, 3, 6, 6,
- 11, 6, 6, 11, 1, 1, 1, 6, 6, 11,
- 3, 3, 3, 6, 8, 6, 3, 3, 11, 6,
- 9, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 2, 2, 2, 1, 3, 2, 3, 3, 3,
+ 6, 6, 11, 6, 6, 11, 1, 1, 1, 6,
+ 6, 11, 3, 3, 3, 6, 8, 6, 3, 3,
+ 11, 6, 9, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ 1, 1
};
@@ -1755,23 +1764,25 @@ namespace isc { namespace eval {
"\"giaddr\"", "\"yiaddr\"", "\"siaddr\"", "\"substring\"", "\"all\"",
"\",\"", "\"concat\"", "\"pkt6\"", "\"msgtype\"", "\"transid\"",
"\"vendor-class\"", "\"vendor\"", "\"*\"", "\"data\"", "\"enterprise\"",
- "\"constant string\"", "\"integer\"", "\"constant hexstring\"",
- "\"option name\"", "\"ip address\"", "$accept", "expression",
- "bool_expr", "string_expr", "integer_expr", "option_code",
- "option_repr_type", "nest_level", "pkt_metadata", "enterprise_id",
- "pkt4_field", "pkt6_field", "relay6_field", "start_expr", "length_expr", YY_NULLPTR
+ "\"top-level bool\"", "\"top-level string\"", "\"constant string\"",
+ "\"integer\"", "\"constant hexstring\"", "\"option name\"",
+ "\"ip address\"", "$accept", "start", "expression", "bool_expr",
+ "string_expr", "integer_expr", "option_code", "option_repr_type",
+ "nest_level", "pkt_metadata", "enterprise_id", "pkt4_field",
+ "pkt6_field", "relay6_field", "start_expr", "length_expr", YY_NULLPTR
};
#if EVALDEBUG
const unsigned short int
EvalParser::yyrline_[] =
{
- 0, 114, 114, 117, 118, 123, 128, 133, 138, 143,
- 163, 177, 186, 195, 207, 212, 217, 222, 227, 248,
- 263, 268, 282, 296, 311, 316, 321, 330, 340, 349,
- 362, 375, 382, 388, 392, 398, 402, 408, 417, 421,
- 425, 429, 435, 439, 445, 449, 453, 457, 461, 465,
- 469, 473, 477, 483, 487, 493, 497, 503, 510, 515
+ 0, 117, 117, 118, 123, 126, 127, 132, 137, 142,
+ 147, 152, 172, 186, 195, 204, 216, 221, 226, 231,
+ 236, 257, 272, 277, 291, 305, 320, 325, 330, 339,
+ 349, 358, 371, 384, 391, 397, 401, 407, 411, 417,
+ 426, 430, 434, 438, 444, 448, 454, 458, 462, 466,
+ 470, 474, 478, 482, 486, 492, 496, 502, 506, 512,
+ 519, 524
};
// Print the state stack on the debug stream.
@@ -1806,8 +1817,8 @@ namespace isc { namespace eval {
#line 14 "parser.yy" // lalr1.cc:1167
} } // isc::eval
-#line 1810 "parser.cc" // lalr1.cc:1167
-#line 522 "parser.yy" // lalr1.cc:1168
+#line 1821 "parser.cc" // lalr1.cc:1167
+#line 531 "parser.yy" // lalr1.cc:1168
void
isc::eval::EvalParser::error(const location_type& loc,
diff --git a/src/lib/eval/parser.h b/src/lib/eval/parser.h
index 11458a5e7b..53df1cb8f2 100644
--- a/src/lib/eval/parser.h
+++ b/src/lib/eval/parser.h
@@ -218,7 +218,7 @@ namespace isc { namespace eval {
/// Both variants must be built beforehand, because swapping the actual
/// data requires reading it (with as()), and this is not possible on
/// unconstructed variants: it would require some dynamic testing, which
- /// should not be the variant's responsability.
+ /// should not be the variant's responsibility.
/// Swapping between built and (possibly) non-built is done with
/// variant::move ().
template <typename T>
@@ -318,22 +318,22 @@ namespace isc { namespace eval {
// relay6_field
char dummy5[sizeof(TokenRelay6Field::FieldType)];
+ // nest_level
+ char dummy6[sizeof(int8_t)];
+
// "constant string"
// "integer"
// "constant hexstring"
// "option name"
// "ip address"
- char dummy6[sizeof(std::string)];
+ char dummy7[sizeof(std::string)];
// option_code
- char dummy7[sizeof(uint16_t)];
+ char dummy8[sizeof(uint16_t)];
// integer_expr
// enterprise_id
- char dummy8[sizeof(uint32_t)];
-
- // nest_level
- char dummy9[sizeof(uint8_t)];
+ char dummy9[sizeof(uint32_t)];
};
/// Symbol semantic values.
@@ -399,11 +399,13 @@ namespace isc { namespace eval {
TOKEN_ANY = 297,
TOKEN_DATA = 298,
TOKEN_ENTERPRISE = 299,
- TOKEN_STRING = 300,
- TOKEN_INTEGER = 301,
- TOKEN_HEXSTRING = 302,
- TOKEN_OPTION_NAME = 303,
- TOKEN_IP_ADDRESS = 304
+ TOKEN_TOPLEVEL_BOOL = 300,
+ TOKEN_TOPLEVEL_STRING = 301,
+ TOKEN_STRING = 302,
+ TOKEN_INTEGER = 303,
+ TOKEN_HEXSTRING = 304,
+ TOKEN_OPTION_NAME = 305,
+ TOKEN_IP_ADDRESS = 306
};
};
@@ -451,14 +453,14 @@ namespace isc { namespace eval {
basic_symbol (typename Base::kind_type t, const TokenRelay6Field::FieldType v, const location_type& l);
+ basic_symbol (typename Base::kind_type t, const int8_t v, const location_type& l);
+
basic_symbol (typename Base::kind_type t, const std::string v, const location_type& l);
basic_symbol (typename Base::kind_type t, const uint16_t v, const location_type& l);
basic_symbol (typename Base::kind_type t, const uint32_t v, const location_type& l);
- basic_symbol (typename Base::kind_type t, const uint8_t v, const location_type& l);
-
/// Constructor for symbols with semantic value.
basic_symbol (typename Base::kind_type t,
@@ -700,6 +702,14 @@ namespace isc { namespace eval {
static inline
symbol_type
+ make_TOPLEVEL_BOOL (const location_type& l);
+
+ static inline
+ symbol_type
+ make_TOPLEVEL_STRING (const location_type& l);
+
+ static inline
+ symbol_type
make_STRING (const std::string& v, const location_type& l);
static inline
@@ -793,7 +803,7 @@ namespace isc { namespace eval {
static const unsigned char yydefact_[];
// YYPGOTO[NTERM-NUM].
- static const signed char yypgoto_[];
+ static const short int yypgoto_[];
// YYDEFGOTO[NTERM-NUM].
static const short int yydefgoto_[];
@@ -923,12 +933,12 @@ namespace isc { namespace eval {
enum
{
yyeof_ = 0,
- yylast_ = 174, ///< Last index in yytable_.
- yynnts_ = 15, ///< Number of nonterminal symbols.
- yyfinal_ = 35, ///< Termination state number.
+ yylast_ = 190, ///< Last index in yytable_.
+ yynnts_ = 16, ///< Number of nonterminal symbols.
+ yyfinal_ = 30, ///< Termination state number.
yyterror_ = 1,
yyerrcode_ = 256,
- yyntokens_ = 50 ///< Number of tokens.
+ yyntokens_ = 52 ///< Number of tokens.
};
@@ -975,9 +985,9 @@ namespace isc { namespace eval {
15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
- 45, 46, 47, 48, 49
+ 45, 46, 47, 48, 49, 50, 51
};
- const unsigned int user_token_number_max_ = 304;
+ const unsigned int user_token_number_max_ = 306;
const token_number_type undef_token_ = 2;
if (static_cast<int>(t) <= yyeof_)
@@ -1010,47 +1020,47 @@ namespace isc { namespace eval {
{
switch (other.type_get ())
{
- case 56: // option_repr_type
+ case 59: // option_repr_type
value.copy< TokenOption::RepresentationType > (other.value);
break;
- case 60: // pkt4_field
+ case 63: // pkt4_field
value.copy< TokenPkt4::FieldType > (other.value);
break;
- case 61: // pkt6_field
+ case 64: // pkt6_field
value.copy< TokenPkt6::FieldType > (other.value);
break;
- case 58: // pkt_metadata
+ case 61: // pkt_metadata
value.copy< TokenPkt::MetadataType > (other.value);
break;
- case 62: // relay6_field
+ case 65: // relay6_field
value.copy< TokenRelay6Field::FieldType > (other.value);
break;
- case 45: // "constant string"
- case 46: // "integer"
- case 47: // "constant hexstring"
- case 48: // "option name"
- case 49: // "ip address"
+ case 60: // nest_level
+ value.copy< int8_t > (other.value);
+ break;
+
+ case 47: // "constant string"
+ case 48: // "integer"
+ case 49: // "constant hexstring"
+ case 50: // "option name"
+ case 51: // "ip address"
value.copy< std::string > (other.value);
break;
- case 55: // option_code
+ case 58: // option_code
value.copy< uint16_t > (other.value);
break;
- case 54: // integer_expr
- case 59: // enterprise_id
+ case 57: // integer_expr
+ case 62: // enterprise_id
value.copy< uint32_t > (other.value);
break;
- case 57: // nest_level
- value.copy< uint8_t > (other.value);
- break;
-
default:
break;
}
@@ -1068,47 +1078,47 @@ namespace isc { namespace eval {
(void) v;
switch (this->type_get ())
{
- case 56: // option_repr_type
+ case 59: // option_repr_type
value.copy< TokenOption::RepresentationType > (v);
break;
- case 60: // pkt4_field
+ case 63: // pkt4_field
value.copy< TokenPkt4::FieldType > (v);
break;
- case 61: // pkt6_field
+ case 64: // pkt6_field
value.copy< TokenPkt6::FieldType > (v);
break;
- case 58: // pkt_metadata
+ case 61: // pkt_metadata
value.copy< TokenPkt::MetadataType > (v);
break;
- case 62: // relay6_field
+ case 65: // relay6_field
value.copy< TokenRelay6Field::FieldType > (v);
break;
- case 45: // "constant string"
- case 46: // "integer"
- case 47: // "constant hexstring"
- case 48: // "option name"
- case 49: // "ip address"
+ case 60: // nest_level
+ value.copy< int8_t > (v);
+ break;
+
+ case 47: // "constant string"
+ case 48: // "integer"
+ case 49: // "constant hexstring"
+ case 50: // "option name"
+ case 51: // "ip address"
value.copy< std::string > (v);
break;
- case 55: // option_code
+ case 58: // option_code
value.copy< uint16_t > (v);
break;
- case 54: // integer_expr
- case 59: // enterprise_id
+ case 57: // integer_expr
+ case 62: // enterprise_id
value.copy< uint32_t > (v);
break;
- case 57: // nest_level
- value.copy< uint8_t > (v);
- break;
-
default:
break;
}
@@ -1160,28 +1170,28 @@ namespace isc { namespace eval {
{}
template <typename Base>
- EvalParser::basic_symbol<Base>::basic_symbol (typename Base::kind_type t, const std::string v, const location_type& l)
+ EvalParser::basic_symbol<Base>::basic_symbol (typename Base::kind_type t, const int8_t v, const location_type& l)
: Base (t)
, value (v)
, location (l)
{}
template <typename Base>
- EvalParser::basic_symbol<Base>::basic_symbol (typename Base::kind_type t, const uint16_t v, const location_type& l)
+ EvalParser::basic_symbol<Base>::basic_symbol (typename Base::kind_type t, const std::string v, const location_type& l)
: Base (t)
, value (v)
, location (l)
{}
template <typename Base>
- EvalParser::basic_symbol<Base>::basic_symbol (typename Base::kind_type t, const uint32_t v, const location_type& l)
+ EvalParser::basic_symbol<Base>::basic_symbol (typename Base::kind_type t, const uint16_t v, const location_type& l)
: Base (t)
, value (v)
, location (l)
{}
template <typename Base>
- EvalParser::basic_symbol<Base>::basic_symbol (typename Base::kind_type t, const uint8_t v, const location_type& l)
+ EvalParser::basic_symbol<Base>::basic_symbol (typename Base::kind_type t, const uint32_t v, const location_type& l)
: Base (t)
, value (v)
, location (l)
@@ -1213,47 +1223,47 @@ namespace isc { namespace eval {
// Type destructor.
switch (yytype)
{
- case 56: // option_repr_type
+ case 59: // option_repr_type
value.template destroy< TokenOption::RepresentationType > ();
break;
- case 60: // pkt4_field
+ case 63: // pkt4_field
value.template destroy< TokenPkt4::FieldType > ();
break;
- case 61: // pkt6_field
+ case 64: // pkt6_field
value.template destroy< TokenPkt6::FieldType > ();
break;
- case 58: // pkt_metadata
+ case 61: // pkt_metadata
value.template destroy< TokenPkt::MetadataType > ();
break;
- case 62: // relay6_field
+ case 65: // relay6_field
value.template destroy< TokenRelay6Field::FieldType > ();
break;
- case 45: // "constant string"
- case 46: // "integer"
- case 47: // "constant hexstring"
- case 48: // "option name"
- case 49: // "ip address"
+ case 60: // nest_level
+ value.template destroy< int8_t > ();
+ break;
+
+ case 47: // "constant string"
+ case 48: // "integer"
+ case 49: // "constant hexstring"
+ case 50: // "option name"
+ case 51: // "ip address"
value.template destroy< std::string > ();
break;
- case 55: // option_code
+ case 58: // option_code
value.template destroy< uint16_t > ();
break;
- case 54: // integer_expr
- case 59: // enterprise_id
+ case 57: // integer_expr
+ case 62: // enterprise_id
value.template destroy< uint32_t > ();
break;
- case 57: // nest_level
- value.template destroy< uint8_t > ();
- break;
-
default:
break;
}
@@ -1277,47 +1287,47 @@ namespace isc { namespace eval {
super_type::move(s);
switch (this->type_get ())
{
- case 56: // option_repr_type
+ case 59: // option_repr_type
value.move< TokenOption::RepresentationType > (s.value);
break;
- case 60: // pkt4_field
+ case 63: // pkt4_field
value.move< TokenPkt4::FieldType > (s.value);
break;
- case 61: // pkt6_field
+ case 64: // pkt6_field
value.move< TokenPkt6::FieldType > (s.value);
break;
- case 58: // pkt_metadata
+ case 61: // pkt_metadata
value.move< TokenPkt::MetadataType > (s.value);
break;
- case 62: // relay6_field
+ case 65: // relay6_field
value.move< TokenRelay6Field::FieldType > (s.value);
break;
- case 45: // "constant string"
- case 46: // "integer"
- case 47: // "constant hexstring"
- case 48: // "option name"
- case 49: // "ip address"
+ case 60: // nest_level
+ value.move< int8_t > (s.value);
+ break;
+
+ case 47: // "constant string"
+ case 48: // "integer"
+ case 49: // "constant hexstring"
+ case 50: // "option name"
+ case 51: // "ip address"
value.move< std::string > (s.value);
break;
- case 55: // option_code
+ case 58: // option_code
value.move< uint16_t > (s.value);
break;
- case 54: // integer_expr
- case 59: // enterprise_id
+ case 57: // integer_expr
+ case 62: // enterprise_id
value.move< uint32_t > (s.value);
break;
- case 57: // nest_level
- value.move< uint8_t > (s.value);
- break;
-
default:
break;
}
@@ -1377,7 +1387,8 @@ namespace isc { namespace eval {
265, 266, 267, 268, 269, 270, 271, 272, 273, 274,
275, 276, 277, 278, 279, 280, 281, 282, 283, 284,
285, 286, 287, 288, 289, 290, 291, 292, 293, 294,
- 295, 296, 297, 298, 299, 300, 301, 302, 303, 304
+ 295, 296, 297, 298, 299, 300, 301, 302, 303, 304,
+ 305, 306
};
return static_cast<token_type> (yytoken_number_[type]);
}
@@ -1641,6 +1652,18 @@ namespace isc { namespace eval {
}
EvalParser::symbol_type
+ EvalParser::make_TOPLEVEL_BOOL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_BOOL, l);
+ }
+
+ EvalParser::symbol_type
+ EvalParser::make_TOPLEVEL_STRING (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_STRING, l);
+ }
+
+ EvalParser::symbol_type
EvalParser::make_STRING (const std::string& v, const location_type& l)
{
return symbol_type (token::TOKEN_STRING, v, l);
@@ -1673,7 +1696,7 @@ namespace isc { namespace eval {
#line 14 "parser.yy" // lalr1.cc:377
} } // isc::eval
-#line 1677 "parser.h" // lalr1.cc:377
+#line 1700 "parser.h" // lalr1.cc:377
diff --git a/src/lib/eval/parser.yy b/src/lib/eval/parser.yy
index 81f012c2e0..142b872b51 100644
--- a/src/lib/eval/parser.yy
+++ b/src/lib/eval/parser.yy
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+/* Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -35,7 +35,7 @@ using namespace isc::eval;
}
%define api.token.prefix {TOKEN_}
-// Tokens in an order which makes sense and related to the intented use.
+// Tokens in an order which makes sense and related to the intended use.
%token
END 0 "end of file"
LPAREN "("
@@ -80,6 +80,9 @@ using namespace isc::eval;
ANY "*"
DATA "data"
ENTERPRISE "enterprise"
+
+ TOPLEVEL_BOOL "top-level bool"
+ TOPLEVEL_STRING "top-level string"
;
%token <std::string> STRING "constant string"
@@ -93,7 +96,7 @@ using namespace isc::eval;
%type <uint32_t> integer_expr
%type <TokenOption::RepresentationType> option_repr_type
%type <TokenRelay6Field::FieldType> relay6_field
-%type <uint8_t> nest_level
+%type <int8_t> nest_level
%type <TokenPkt::MetadataType> pkt_metadata
%type <TokenPkt4::FieldType> pkt4_field
%type <TokenPkt6::FieldType> pkt6_field
@@ -106,8 +109,14 @@ using namespace isc::eval;
%%
-// The whole grammar starts with an expression.
-%start expression;
+// The whole grammar starts with a 'start' symbol...
+%start start;
+
+// ... that expects either TOPLEVEL_BOOL or TOPLEVEL_STRING. Depending on which
+// token appears first, it will determine what is allowed and what it not.
+start: TOPLEVEL_BOOL expression
+ | TOPLEVEL_STRING string_expr
+;
// Expression can either be a single token or a (something == something) expression
diff --git a/src/lib/eval/position.hh b/src/lib/eval/position.hh
index b775b5ce57..8b6fbea73c 100644
--- a/src/lib/eval/position.hh
+++ b/src/lib/eval/position.hh
@@ -1,4 +1,4 @@
-// Generated 201611011345
+// Generated 201707051218
// A Bison parser, made by GNU Bison 3.0.4.
// Positions for Bison parsers in C++
diff --git a/src/lib/eval/stack.hh b/src/lib/eval/stack.hh
index 71853729b2..6ebdc5161a 100644
--- a/src/lib/eval/stack.hh
+++ b/src/lib/eval/stack.hh
@@ -1,4 +1,4 @@
-// Generated 201611011345
+// Generated 201707051218
// A Bison parser, made by GNU Bison 3.0.4.
// Stack handling for Bison parsers in C++
diff --git a/src/lib/eval/tests/boolean_unittest.cc b/src/lib/eval/tests/boolean_unittest.cc
index 3acf81843f..f620daeca4 100644
--- a/src/lib/eval/tests/boolean_unittest.cc
+++ b/src/lib/eval/tests/boolean_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -27,9 +27,9 @@ public:
ASSERT_TRUE(eval.parseString(expr));
Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
if (expected) {
- EXPECT_TRUE(evaluate(eval.expression, *pkt4));
+ EXPECT_TRUE(evaluateBool(eval.expression, *pkt4));
} else {
- EXPECT_FALSE(evaluate(eval.expression, *pkt4));
+ EXPECT_FALSE(evaluateBool(eval.expression, *pkt4));
}
}
};
diff --git a/src/lib/eval/tests/context_unittest.cc b/src/lib/eval/tests/context_unittest.cc
index 5280b20274..40252f8afb 100644
--- a/src/lib/eval/tests/context_unittest.cc
+++ b/src/lib/eval/tests/context_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -151,7 +151,7 @@ public:
/// @param expected_code expected option code
/// @param expected_repr expected representation (text, hex, exists)
void checkTokenRelay6Option(const TokenPtr& token,
- uint8_t expected_level,
+ int8_t expected_level,
uint16_t expected_code,
TokenOption::RepresentationType expected_repr) {
ASSERT_TRUE(token);
@@ -174,7 +174,7 @@ public:
/// @param exp_repr expected representation to be parsed
/// @param exp_tokens expected number of tokens
void testRelay6Option(const std::string& expr,
- uint8_t exp_level,
+ int8_t exp_level,
uint16_t exp_code,
TokenOption::RepresentationType exp_repr,
int exp_tokens) {
@@ -189,13 +189,13 @@ public:
return;
}
- // Parsing should succed and return a token.
+ // Parsing should succeed and return a token.
EXPECT_TRUE(parsed_);
// There should be the expected number of tokens.
ASSERT_EQ(exp_tokens, eval.expression.size());
- // checkt that the first token is TokenRelay6Option and that
+ // checked that the first token is TokenRelay6Option and that
// is has the correct attributes
checkTokenRelay6Option(eval.expression.at(0), exp_level, exp_code, exp_repr);
}
@@ -212,7 +212,7 @@ public:
EXPECT_EQ(type, pkt->getType());
}
- /// @brief Test that verifies access to the DHCP packet metadatas.
+ /// @brief Test that verifies access to the DHCP packet metadata.
///
/// This test attempts to parse the expression, will check if the number
/// of tokens is exactly as expected and then will try to verify if the
@@ -345,7 +345,7 @@ public:
/// @param expected_code expected option code
/// @param expected_repr expected representation (text, hex, exists)
void checkTokenRelay6Field(const TokenPtr& token,
- uint8_t expected_level,
+ int8_t expected_level,
TokenRelay6Field::FieldType expected_type) {
ASSERT_TRUE(token);
boost::shared_ptr<TokenRelay6Field> opt =
@@ -365,7 +365,7 @@ public:
/// @param exp_type expected field type to be parsed
/// @param exp_tokens expected number of tokens
void testRelay6Field(const std::string& expr,
- uint8_t exp_level,
+ int8_t exp_level,
TokenRelay6Field::FieldType exp_type,
int exp_tokens) {
EvalContext eval(Option::V6);
@@ -379,13 +379,13 @@ public:
return;
}
- // Parsing should succed and return a token.
+ // Parsing should succeed and return a token.
EXPECT_TRUE(parsed_);
// There should be the expected number of tokens.
ASSERT_EQ(exp_tokens, eval.expression.size());
- // checkt that the first token is TokenRelay6Field and that
+ // checked that the first token is TokenRelay6Field and that
// is has the correct attributes
checkTokenRelay6Field(eval.expression.at(0), exp_level, exp_type);
}
@@ -934,7 +934,15 @@ TEST_F(EvalContextTest, relay6OptionHex) {
2, 85, TokenOption::HEXADECIMAL, 3);
}
-// Test the nest level of a relay6 option should be in [0..32[
+// Test the parsing of a relay6 option in reverse order
+TEST_F(EvalContextTest, relay6OptionReverse) {
+ EvalContext eval(Option::V6);
+
+ testRelay6Option("relay6[-1].option[123].text == 'foo'",
+ -1, 123, TokenOption::TEXTUAL, 3);
+}
+
+// Test the nest level of a relay6 option should be in [-32..32[
TEST_F(EvalContextTest, relay6OptionLimits) {
EvalContext eval(Option::V6);
@@ -946,11 +954,14 @@ TEST_F(EvalContextTest, relay6OptionLimits) {
checkError("relay6[32].option[123].text == 'foo'",
"<string>:1.8-9: Nest level has invalid value in 32. "
- "Allowed range: 0..31");
+ "Allowed range: -32..31");
+
+ // min nest level is minus hop count limit
+ testRelay6Option("relay6[-32].option[123].text == 'foo'",
+ -32, 123, TokenOption::TEXTUAL, 3);
- // next level must be a positive number
- checkError("relay6[-1].option[123].text == 'foo'",
- "<string>:1.8-9: Invalid value in -1. Allowed range: 0..255");
+ checkError("relay6[-33].option[123].text == 'foo'",
+ "<string>:1.8-10: Nest level has invalid value in -33. Allowed range: -32..31");
}
// Verify that relay6[13].option is not usable in v4
@@ -1041,7 +1052,7 @@ TEST_F(EvalContextTest, relay6FieldPeerAddr) {
1, TokenRelay6Field::PEERADDR, 3);
}
-// Verify that relay6[13].<field> is not usable in v4
+// Verify that relay6[0].<field> is not usable in v4
TEST_F(EvalContextTest, relay6FieldError) {
universe_ = Option::V4;
@@ -1207,7 +1218,10 @@ TEST_F(EvalContextTest, scanErrors) {
checkError("0x123h", "<string>:1.6: Invalid character: h");
checkError(":1", "<string>:1.1: Invalid character: :");
checkError("=", "<string>:1.1: Invalid character: =");
+
+ // Typo should be handled as well.
checkError("subtring", "<string>:1.1: Invalid character: s");
+
checkError("foo", "<string>:1.1: Invalid character: f");
checkError(" bar", "<string>:1.2: Invalid character: b");
checkError("relay[12].hex == 'foo'", "<string>:1.1: Invalid character: r");
@@ -1359,7 +1373,7 @@ TEST_F(EvalContextTest, typeErrors) {
"hexstring, expecting integer");
// With the #4483 addition, all integers are treated as 4 byte strings,
- // so those checks no longer makes sense. Commeting it out.
+ // so those checks no longer makes sense. Commenting it out.
// checkError("concat('foo',3) == 'foo3'",
// "<string>:1.14: syntax error, unexpected integer");
// checkError("concat(3,'foo') == '3foo'",
diff --git a/src/lib/eval/tests/evaluate_unittest.cc b/src/lib/eval/tests/evaluate_unittest.cc
index 64950c9704..6799770eb4 100644
--- a/src/lib/eval/tests/evaluate_unittest.cc
+++ b/src/lib/eval/tests/evaluate_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -64,13 +64,13 @@ public:
// This checks the empty expression: it should raise EvalBadStack
// when evaluated with a Pkt4. (The actual packet is not used)
TEST_F(EvaluateTest, empty4) {
- ASSERT_THROW(evaluate(e_, *pkt4_), EvalBadStack);
+ ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalBadStack);
}
// This checks the empty expression: it should raise EvalBadStack
// when evaluated with a Pkt6. (The actual packet is not used)
TEST_F(EvaluateTest, empty6) {
- ASSERT_THROW(evaluate(e_, *pkt6_), EvalBadStack);
+ ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalBadStack);
}
// This checks the { "false" } expression: it should return false
@@ -79,7 +79,7 @@ TEST_F(EvaluateTest, false4) {
TokenPtr tfalse;
ASSERT_NO_THROW(tfalse.reset(new TokenString("false")));
e_.push_back(tfalse);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
EXPECT_FALSE(result_);
}
@@ -89,7 +89,7 @@ TEST_F(EvaluateTest, false6) {
TokenPtr tfalse;
ASSERT_NO_THROW(tfalse.reset(new TokenString("false")));
e_.push_back(tfalse);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
EXPECT_FALSE(result_);
}
@@ -99,7 +99,7 @@ TEST_F(EvaluateTest, true4) {
TokenPtr ttrue;
ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
e_.push_back(ttrue);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
EXPECT_TRUE(result_);
}
@@ -109,7 +109,7 @@ TEST_F(EvaluateTest, true6) {
TokenPtr ttrue;
ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
e_.push_back(ttrue);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
EXPECT_TRUE(result_);
}
@@ -119,7 +119,7 @@ TEST_F(EvaluateTest, bad4) {
TokenPtr bad;
ASSERT_NO_THROW(bad.reset(new TokenString("bad")));
e_.push_back(bad);
- ASSERT_THROW(evaluate(e_, *pkt4_), EvalTypeError);
+ ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalTypeError);
}
// This checks the evaluation must lead to "false" or "true"
@@ -128,7 +128,7 @@ TEST_F(EvaluateTest, bad6) {
TokenPtr bad;
ASSERT_NO_THROW(bad.reset(new TokenString("bad")));
e_.push_back(bad);
- ASSERT_THROW(evaluate(e_, *pkt6_), EvalTypeError);
+ ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalTypeError);
}
// This checks the evaluation must leave only one value on the stack
@@ -138,7 +138,7 @@ TEST_F(EvaluateTest, two4) {
ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
e_.push_back(ttrue);
e_.push_back(ttrue);
- ASSERT_THROW(evaluate(e_, *pkt4_), EvalBadStack);
+ ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalBadStack);
}
// This checks the evaluation must leave only one value on the stack
@@ -148,7 +148,7 @@ TEST_F(EvaluateTest, two6) {
ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
e_.push_back(ttrue);
e_.push_back(ttrue);
- ASSERT_THROW(evaluate(e_, *pkt6_), EvalBadStack);
+ ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalBadStack);
}
// A more complex test evaluated with a Pkt4. (The actual packet is not used)
@@ -164,7 +164,7 @@ TEST_F(EvaluateTest, compare4) {
ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
e_.push_back(tequal);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
EXPECT_FALSE(result_);
}
@@ -181,7 +181,7 @@ TEST_F(EvaluateTest, compare6) {
ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
e_.push_back(tequal);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
EXPECT_FALSE(result_);
}
@@ -192,9 +192,9 @@ TEST_F(EvaluateTest, exists) {
ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::EXISTS)));
e_.push_back(toption);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
EXPECT_TRUE(result_);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
EXPECT_TRUE(result_);
}
@@ -205,9 +205,9 @@ TEST_F(EvaluateTest, dontExists) {
ASSERT_NO_THROW(toption.reset(new TokenOption(101, TokenOption::EXISTS)));
e_.push_back(toption);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
EXPECT_FALSE(result_);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
EXPECT_FALSE(result_);
}
@@ -224,9 +224,9 @@ TEST_F(EvaluateTest, packet) {
ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
e_.push_back(tequal);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
EXPECT_TRUE(result_);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
EXPECT_FALSE(result_);
}
@@ -243,9 +243,9 @@ TEST_F(EvaluateTest, optionHex) {
ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
e_.push_back(tequal);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
EXPECT_TRUE(result_);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
EXPECT_FALSE(result_);
}
@@ -277,9 +277,9 @@ TEST_F(EvaluateTest, complex) {
e_.push_back(tequal);
// Should return true for v4 and v6 packets
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt4_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
EXPECT_TRUE(result_);
- ASSERT_NO_THROW(result_ = evaluate(e_, *pkt6_));
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
EXPECT_TRUE(result_);
}
@@ -294,7 +294,7 @@ TEST_F(EvaluateTest, complex) {
class ExpressionsTest : public EvaluateTest {
public:
- /// @brief Checks if expression can be parsed and evaluated
+ /// @brief Checks if expression can be parsed and evaluated to bool
///
/// There are skeleton packets created in pkt4_ and pkt6_. Make sure you
/// tweak them as needed before calling this method.
@@ -315,11 +315,44 @@ public:
switch (u) {
case Option::V4:
- ASSERT_NO_THROW(result = evaluate(eval.expression, *pkt4_))
+ ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt4_))
<< " for expression " << expr;
break;
case Option::V6:
- ASSERT_NO_THROW(result = evaluate(eval.expression, *pkt6_))
+ ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt6_))
+ << " for expression " << expr;
+ break;
+ }
+
+ EXPECT_EQ(exp_result, result) << " for expression " << expr;
+ }
+
+ /// @brief Checks if expression can be parsed and evaluated to string
+ ///
+ /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you
+ /// tweak them as needed before calling this method.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param expr expression to be parsed
+ /// @param exp_result expected result (string)
+ void testExpressionString(const Option::Universe& u, const std::string& expr,
+ const std::string& exp_result) {
+
+ EvalContext eval(u);
+ string result;
+ bool parsed = false;
+
+ EXPECT_NO_THROW(parsed = eval.parseString(expr, EvalContext::PARSER_STRING))
+ << " while parsing expression " << expr;
+ EXPECT_TRUE(parsed) << " for expression " << expr;
+
+ switch (u) {
+ case Option::V4:
+ ASSERT_NO_THROW(result = evaluateString(eval.expression, *pkt4_))
+ << " for expression " << expr;
+ break;
+ case Option::V6:
+ ASSERT_NO_THROW(result = evaluateString(eval.expression, *pkt6_))
<< " for expression " << expr;
break;
}
@@ -333,10 +366,11 @@ public:
/// @param expr expression to be evaluated
template<typename ex>
void testExpressionNegative(const std::string& expr,
- const Option::Universe& u = Option::V4) {
+ const Option::Universe& u = Option::V4,
+ EvalContext::ParserType type = EvalContext::PARSER_BOOL) {
EvalContext eval(u);
- EXPECT_THROW(eval.parseString(expr), ex) << "while parsing expression "
+ EXPECT_THROW(eval.parseString(expr, type), ex) << "while parsing expression "
<< expr;
}
};
@@ -440,4 +474,22 @@ TEST_F(ExpressionsTest, invalidIntegers) {
// Oops, one too much.
testExpressionNegative<EvalParseError>("4294967296 == 0");
}
+
+// Tests whether expressions can be evaluated to a string.
+TEST_F(ExpressionsTest, evaluateString) {
+
+ // Check that content of the options is returned properly.
+ testExpressionString(Option::V4, "option[100].hex", "hundred4");
+ testExpressionString(Option::V6, "option[100].hex", "hundred6");
+
+ // Check that content of non-existing option returns empty string.
+ testExpressionString(Option::V4, "option[200].hex", "");
+ testExpressionString(Option::V6, "option[200].hex", "");
+
+ testExpressionNegative<EvalParseError>("pkt4.msgtype == 1", Option::V4,
+ EvalContext::PARSER_STRING);
+ testExpressionNegative<EvalParseError>("pkt6.msgtype == 1", Option::V6,
+ EvalContext::PARSER_STRING);
+}
+
};
diff --git a/src/lib/eval/tests/token_unittest.cc b/src/lib/eval/tests/token_unittest.cc
index c0b96fb8e1..d1e99798b1 100644
--- a/src/lib/eval/tests/token_unittest.cc
+++ b/src/lib/eval/tests/token_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -120,7 +120,7 @@ public:
pkt6_->addRelayInfo(relay1);
}
- /// @brief Verify that the relay6 option evaluatiosn work properly
+ /// @brief Verify that the relay6 option evaluations work properly
///
/// Given the nesting level and option code extract the option
/// and compare it to the expected string.
@@ -128,7 +128,7 @@ public:
/// @param test_level The nesting level
/// @param test_code The code of the option to extract
/// @param result_addr The expected result of the address as a string
- void verifyRelay6Option(const uint8_t test_level,
+ void verifyRelay6Option(const int8_t test_level,
const uint16_t test_code,
const TokenOption::RepresentationType& test_rep,
const std::string& result_string) {
@@ -159,7 +159,7 @@ public:
/// @param test_level The nesting level
/// @param test_field The type of the field to extract
/// @param result_addr The expected result of the address as a string
- void verifyRelay6Eval(const uint8_t test_level,
+ void verifyRelay6Eval(const int8_t test_level,
const TokenRelay6Field::FieldType test_field,
const int result_len,
const uint8_t result_addr[]) {
@@ -301,7 +301,7 @@ public:
OpaqueDataTuple tuple(len);
tuple.assign(string(content[i]));
if (u == Option::V4 && i == 0) {
- // vendor-clas for v4 has a pecurilar quirk. The first tuple is being
+ // vendor-class for v4 has a peculiar quirk. The first tuple is being
// added, even if there's no data at all.
vendor_class_->setTuple(0, tuple);
} else {
@@ -1135,6 +1135,21 @@ TEST_F(TokenTest, relay6Option) {
// Level 2, no encapsulation so no options
verifyRelay6Option(2, 100, TokenOption::TEXTUAL, "");
+ // Level -1, the same as level 1
+ verifyRelay6Option(-1, 100, TokenOption::TEXTUAL, "hundred.one");
+ verifyRelay6Option(-1, 101, TokenOption::TEXTUAL, "");
+ verifyRelay6Option(-1, 102, TokenOption::TEXTUAL, "hundredtwo.one");
+
+ // Level -2, the same as level 0
+ verifyRelay6Option(-2, 100, TokenOption::TEXTUAL, "hundred.zero");
+ verifyRelay6Option(-2, 100, TokenOption::EXISTS, "true");
+ verifyRelay6Option(-2, 101, TokenOption::TEXTUAL, "hundredone.zero");
+ verifyRelay6Option(-2, 102, TokenOption::TEXTUAL, "");
+ verifyRelay6Option(-2, 102, TokenOption::EXISTS, "false");
+
+ // Level -3, no encapsulation so no options
+ verifyRelay6Option(-3, 100, TokenOption::TEXTUAL, "");
+
// Check that the debug output was correct. Add the strings
// to the test vector in the class and then call checkFile
// for comparison
@@ -1150,6 +1165,18 @@ TEST_F(TokenTest, relay6Option) {
addString("EVAL_DEBUG_OPTION Pushing option 100 with value ''");
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred.one'");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''");
+ addString("EVAL_DEBUG_OPTION Pushing option 102 with value 'hundredtwo.one'");
+
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred.zero'");
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'hundredone.zero'");
+ addString("EVAL_DEBUG_OPTION Pushing option 102 with value ''");
+ addString("EVAL_DEBUG_OPTION Pushing option 102 with value 'false'");
+
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value ''");
+
EXPECT_TRUE(checkFile());
}
@@ -1465,6 +1492,17 @@ TEST_F(TokenTest, relay6Field) {
// Level 2 has no encapsulation so the address should be zero length
verifyRelay6Eval(2, TokenRelay6Field::LINKADDR, 0, zeroaddr);
+ // Level -1 is the same as level 1
+ verifyRelay6Eval(-1, TokenRelay6Field::LINKADDR, 16, linkaddr);
+ verifyRelay6Eval(-1, TokenRelay6Field::PEERADDR, 16, peeraddr);
+
+ // Level -2 is the same as level 0
+ verifyRelay6Eval(-2, TokenRelay6Field::LINKADDR, 16, zeroaddr);
+ verifyRelay6Eval(-2, TokenRelay6Field::PEERADDR, 16, zeroaddr);
+
+ // Level -3 has no encapsulation so the address should be zero length
+ verifyRelay6Eval(-3, TokenRelay6Field::LINKADDR, 0, zeroaddr);
+
// Lets check that the layout of the address returned by the
// token matches that of the TokenIpAddress
TokenPtr trelay;
@@ -1496,8 +1534,19 @@ TEST_F(TokenTest, relay6Field) {
"with value 0x00010000000000000000000000000001");
addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest 1 "
"with value 0x00010000000000000000000000000002");
- addString("EVAL_DEBUG_RELAY6_RANGE Pushing PKT6 relay field linkaddr nest 2 "
- "with value 0x");
+ addString("EVAL_DEBUG_RELAY6_RANGE Pushing PKT6 relay field linkaddr "
+ "nest 2 with value 0x");
+
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest -1 "
+ "with value 0x00010000000000000000000000000001");
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest -1 "
+ "with value 0x00010000000000000000000000000002");
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest -2 "
+ "with value 0x00000000000000000000000000000000");
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest -2 "
+ "with value 0x00000000000000000000000000000000");
+ addString("EVAL_DEBUG_RELAY6_RANGE Pushing PKT6 relay field linkaddr "
+ "nest -3 with value 0x");
addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest 1 "
"with value 0x00010000000000000000000000000001");
@@ -1546,7 +1595,7 @@ TEST_F(TokenTest, optionEqualFalse) {
EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
// After evaluation there should be a single value that represents
- // result of "foo" == "bar" comparision.
+ // result of "foo" == "bar" comparison.
ASSERT_EQ(1, values_.size());
EXPECT_EQ("false", values_.top());
@@ -1569,7 +1618,7 @@ TEST_F(TokenTest, optionEqualTrue) {
EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
// After evaluation there should be a single value that represents
- // result of "foo" == "foo" comparision.
+ // result of "foo" == "foo" comparison.
ASSERT_EQ(1, values_.size());
EXPECT_EQ("true", values_.top());
@@ -1588,7 +1637,7 @@ TEST_F(TokenTest, optionEqualTrue) {
TEST_F(TokenTest, substringNotEnoughValues) {
ASSERT_NO_THROW(t_.reset(new TokenSubstring()));
- // Subsring requires three values on the stack, try
+ // Substring requires three values on the stack, try
// with 0, 1 and 2 all should throw an exception
EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
@@ -1813,7 +1862,7 @@ TEST_F(TokenTest, substringReturnEmpty) {
// We put the result on the stack first then the substring values
// then evaluate the substring which should leave the original
// result on the bottom with the substring result on next.
-// Evaulating the equals should produce true for the first
+// Evaluating the equals should produce true for the first
// and false for the second.
// throws an exception if there aren't enough values on the stack.
// The stack from the top is: length, start, string.
@@ -2026,7 +2075,7 @@ TEST_F(TokenTest, operatorAndTrue) {
}
// This test checks if a token representing an or is able to
-// combinate two values (with incorrectly built stack).
+// combine two values (with incorrectly built stack).
TEST_F(TokenTest, operatorOrInvalid) {
ASSERT_NO_THROW(t_.reset(new TokenOr()));
@@ -2134,7 +2183,7 @@ TEST_F(TokenTest, vendor6SpecificVendorExists) {
// Case 2: option present, but uses different enterprise-id, should fail
testVendorExists(Option::V6, 4491, 1234, "false");
- // Case 3: option present and has matchin enterprise-id, should suceed
+ // Case 3: option present and has matchin enterprise-id, should succeed
testVendorExists(Option::V6, 4491, 4491, "true");
// Check if the logged messages are correct.
diff --git a/src/lib/eval/token.cc b/src/lib/eval/token.cc
index c1812c5dca..1366cab464 100644
--- a/src/lib/eval/token.cc
+++ b/src/lib/eval/token.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -179,7 +179,17 @@ OptionPtr TokenRelay6Option::getOption(Pkt& pkt) {
try {
// Now that we have the right type of packet we can
// get the option and return it.
- return(pkt6.getRelayOption(option_code_, nest_level_));
+ if (nest_level_ >= 0) {
+ uint8_t nesting_level = static_cast<uint8_t>(nest_level_);
+ return(pkt6.getRelayOption(option_code_, nesting_level));
+ } else {
+ int nesting_level = pkt6.relay_info_.size() + nest_level_;
+ if (nesting_level < 0) {
+ return (OptionPtr());
+ }
+ return(pkt6.getRelayOption(option_code_,
+ static_cast<uint8_t>(nesting_level)));
+ }
}
catch (const isc::OutOfRange&) {
// The only exception we expect is OutOfRange if the nest
@@ -383,18 +393,29 @@ TokenRelay6Field::evaluate(Pkt& pkt, ValueStack& values) {
// Check if it's a Pkt6. If it's not the dynamic_cast will
// throw std::bad_cast.
const Pkt6& pkt6 = dynamic_cast<const Pkt6&>(pkt);
+ uint8_t relay_level;
try {
- switch (type_) {
+ if (nest_level_ >= 0) {
+ relay_level = static_cast<uint8_t>(nest_level_);
+ } else {
+ int nesting_level = pkt6.relay_info_.size() + nest_level_;
+ if (nesting_level < 0) {
+ // Don't throw OutOfRange here
+ nesting_level = 32;
+ }
+ relay_level = static_cast<uint8_t>(nesting_level);
+ }
+ switch (type_) {
// Now that we have the right type of packet we can
// get the option and return it.
case LINKADDR:
type_str = "linkaddr";
- binary = pkt6.getRelay6LinkAddress(nest_level_).toBytes();
+ binary = pkt6.getRelay6LinkAddress(relay_level).toBytes();
break;
case PEERADDR:
type_str = "peeraddr";
- binary = pkt6.getRelay6PeerAddress(nest_level_).toBytes();
+ binary = pkt6.getRelay6PeerAddress(relay_level).toBytes();
break;
}
} catch (const isc::OutOfRange&) {
@@ -404,7 +425,7 @@ TokenRelay6Field::evaluate(Pkt& pkt, ValueStack& values) {
// Log what we pushed
LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_RELAY6_RANGE)
.arg(type_str)
- .arg(unsigned(nest_level_))
+ .arg(int(nest_level_))
.arg("0x");
return;
}
@@ -422,7 +443,7 @@ TokenRelay6Field::evaluate(Pkt& pkt, ValueStack& values) {
// Log what we pushed
LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_RELAY6)
.arg(type_str)
- .arg(unsigned(nest_level_))
+ .arg(int(nest_level_))
.arg(toHex(value));
}
diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h
index 031a4ad64f..6d4ec95f1c 100644
--- a/src/lib/eval/token.h
+++ b/src/lib/eval/token.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -338,16 +338,17 @@ protected:
/// exist or the option is not found an empty string ("") is returned
/// (or "false" when the representation is EXISTS).
///
-/// The nesting level can go from 0 (closest to the server) to 31
+/// The nesting level can go from 0 (closest to the server) to 31,
+/// or from -1 (closest to the client) to -32
class TokenRelay6Option : public TokenOption {
public:
/// @brief Constructor that takes a nesting level and an option
- /// code as paramaters.
+ /// code as parameters.
///
/// @param nest_level the nesting for which relay to examine.
/// @param option_code code of the option.
/// @param rep_type Token representation type.
- TokenRelay6Option(const uint8_t nest_level, const uint16_t option_code,
+ TokenRelay6Option(const int8_t nest_level, const uint16_t option_code,
const RepresentationType& rep_type)
:TokenOption(option_code, rep_type), nest_level_(nest_level) {}
@@ -358,7 +359,7 @@ public:
///
/// @return nest-level of the relay block this token expects to use
/// for extraction.
- uint8_t getNest() const {
+ int8_t getNest() const {
return (nest_level_);
}
@@ -368,7 +369,7 @@ protected:
/// @return option instance if available
virtual OptionPtr getOption(Pkt& pkt);
- uint8_t nest_level_; ///< nesting level of the relay block to use
+ int8_t nest_level_; ///< nesting level of the relay block to use
};
/// @brief Token that represents meta data of a DHCP packet.
@@ -398,10 +399,10 @@ public:
/// @brief Gets a value from the specified packet.
///
- /// Evaluation uses metadatas available in the packet. It does not
+ /// Evaluation uses metadata available in the packet. It does not
/// require any values to be present on the stack.
///
- /// @param pkt - metadatas will be extracted from here
+ /// @param pkt - metadata will be extracted from here
/// @param values - stack of values (1 result will be pushed)
void evaluate(Pkt& pkt, ValueStack& values);
@@ -499,7 +500,7 @@ public:
/// @brief Gets a value of the specified packet.
///
- /// The evaluation uses fields that are availabe in the packet. It does not
+ /// The evaluation uses fields that are available in the packet. It does not
/// require any values to be present on the stack.
///
/// @throw EvalTypeError when called for a DHCPv4 packet
@@ -534,7 +535,8 @@ private:
/// is always returned as a 16 byte IPv6 address. As the relay may not have
/// set the field it may be 0s.
///
-/// The nesting level can go from 0 (closest to the server) to 31.
+/// The nesting level can go from 0 (closest to the server) to 31,
+/// or from -1 (closest to the client) to -32
class TokenRelay6Field : public Token {
public:
@@ -549,7 +551,7 @@ public:
///
/// @param nest_level the nesting level for which relay to examine.
/// @param type which field to extract.
- TokenRelay6Field(const uint8_t nest_level, const FieldType type)
+ TokenRelay6Field(const int8_t nest_level, const FieldType type)
: nest_level_(nest_level), type_(type) {}
/// @brief Extracts the specified field from the requested relay
@@ -568,7 +570,7 @@ public:
///
/// @return nest-level of the relay block this token expects to use
/// for extraction.
- uint8_t getNest() const {
+ int8_t getNest() const {
return (nest_level_);
}
@@ -584,7 +586,7 @@ public:
protected:
/// @brief Specifies field of the DHCPv6 relay option to get
- uint8_t nest_level_; ///< nesting level of the relay block to use
+ int8_t nest_level_; ///< nesting level of the relay block to use
FieldType type_; ///< field to get
};
diff --git a/src/lib/exceptions/exceptions.h b/src/lib/exceptions/exceptions.h
index 2f044e6a19..b6396908cf 100644
--- a/src/lib/exceptions/exceptions.h
+++ b/src/lib/exceptions/exceptions.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -64,7 +64,7 @@ public:
/// @return A C-style character string of the exception cause.
virtual const char* what() const throw();
- /// \brief Returns a C-style charater string of the cause of exception.
+ /// \brief Returns a C-style character string of the cause of exception.
///
/// With verbose set to true, also returns file name and line numbers.
/// Note that we can't simply define a single what() method with parameters,
@@ -241,6 +241,6 @@ public:
}
#endif // EXCEPTIONS_H
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/exceptions/tests/exceptions_unittest.cc b/src/lib/exceptions/tests/exceptions_unittest.cc
index 381b84eb02..e2e632b91b 100644
--- a/src/lib/exceptions/tests/exceptions_unittest.cc
+++ b/src/lib/exceptions/tests/exceptions_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -36,6 +36,17 @@ TEST_F(ExceptionTest, basicMethods) {
}
}
+// Same than basicMethods but using a string (vs char *)
+TEST_F(ExceptionTest, string) {
+ try {
+ isc_throw(Exception, std::string(teststring));
+ } catch (Exception& ex) {
+ EXPECT_EQ(ex.getMessage(), std::string(teststring));
+ EXPECT_EQ(ex.getFile(), std::string(__FILE__));
+ EXPECT_EQ(ex.getLine(), __LINE__ - 4);
+ }
+}
+
// Test to see if it works as a proper derived class of std::exception.
TEST_F(ExceptionTest, stdInheritance) {
try {
diff --git a/src/lib/hooks/Makefile.am b/src/lib/hooks/Makefile.am
index 624e227205..50ba43d453 100644
--- a/src/lib/hooks/Makefile.am
+++ b/src/lib/hooks/Makefile.am
@@ -36,6 +36,8 @@ libkea_hooks_la_SOURCES += callout_manager.cc callout_manager.h
libkea_hooks_la_SOURCES += hooks.h
libkea_hooks_la_SOURCES += hooks_log.cc hooks_log.h
libkea_hooks_la_SOURCES += hooks_manager.cc hooks_manager.h
+libkea_hooks_la_SOURCES += hooks_config.cc hooks_config.h
+libkea_hooks_la_SOURCES += hooks_parser.cc hooks_parser.h
libkea_hooks_la_SOURCES += libinfo.cc libinfo.h
libkea_hooks_la_SOURCES += library_handle.cc library_handle.h
libkea_hooks_la_SOURCES += library_manager.cc library_manager.h
@@ -47,7 +49,7 @@ nodist_libkea_hooks_la_SOURCES = hooks_messages.cc hooks_messages.h
libkea_hooks_la_CXXFLAGS = $(AM_CXXFLAGS)
libkea_hooks_la_CPPFLAGS = $(AM_CPPFLAGS)
-libkea_hooks_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 2:0:0
+libkea_hooks_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 3:0:0
libkea_hooks_la_LIBADD =
libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
diff --git a/src/lib/hooks/callout_manager.cc b/src/lib/hooks/callout_manager.cc
index d41f7df443..e0b3bc073d 100644
--- a/src/lib/hooks/callout_manager.cc
+++ b/src/lib/hooks/callout_manager.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -104,6 +104,27 @@ CalloutManager::calloutsPresent(int hook_index) const {
return (!hook_vector_[hook_index].empty());
}
+bool
+CalloutManager::commandHandlersPresent(const std::string& command_name) const {
+ // Check if the hook point for the specified command exists.
+ int index = ServerHooks::getServerHooks().findIndex(
+ ServerHooks::commandToHookName(command_name));
+ if (index >= 0) {
+ // The hook point exits but it is possible that there are no
+ // callouts/command handlers. This is possible if there was a
+ // hook library supporting this command attached, but it was
+ // later unloaded. The hook points are not deregistered in
+ // this case. Only callouts are deregistered.
+ // Let's check if callouts are present for this hook point.
+ return (calloutsPresent(index));
+ }
+
+ // Hook point not created, so we don't support this command in
+ // any of the hooks libraries.
+ return (false);
+}
+
+
// Call all the callouts for a given hook.
void
@@ -184,13 +205,27 @@ CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
.arg(server_hooks_.getName(current_hook_))
.arg(stopwatch.logFormatTotalDuration());
- // Reset the current hook and library indexs to an invalid value to
+ // Reset the current hook and library indexes to an invalid value to
// catch any programming errors.
current_hook_ = -1;
current_library_ = -1;
}
}
+void
+CalloutManager::callCommandHandlers(const std::string& command_name,
+ CalloutHandle& callout_handle) {
+ // Get the index of the hook point for the specified command.
+ // This will throw an exception if the hook point doesn't exist.
+ // The caller should check if the hook point exists by calling
+ // commandHandlersPresent.
+ int index = ServerHooks::getServerHooks().getIndex(
+ ServerHooks::commandToHookName(command_name));
+ // Call the handlers for this command.
+ callCallouts(index, callout_handle);
+}
+
+
// Deregister a callout registered by the current library on a particular hook.
bool
@@ -272,5 +307,21 @@ CalloutManager::deregisterAllCallouts(const std::string& name) {
return (removed);
}
+void
+CalloutManager::registerCommandHook(const std::string& command_name) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ int hook_index = hooks.findIndex(ServerHooks::commandToHookName(command_name));
+ if (hook_index < 0) {
+ // Hook for this command doesn't exist. Let's create one.
+ hooks.registerHook(ServerHooks::commandToHookName(command_name));
+ // Callout Manager's vector of hooks have to be resized to hold the
+ // information about callouts for this new hook point. This should
+ // add new element at the end of the hook_vector_. The index of this
+ // element will match the index of the hook point in the ServerHooks
+ // because ServerHooks allocates indexes incrementally.
+ hook_vector_.resize(server_hooks_.getCount());
+ }
+}
+
} // namespace util
} // namespace isc
diff --git a/src/lib/hooks/callout_manager.h b/src/lib/hooks/callout_manager.h
index 843e1b9cc8..1cf9cdb2eb 100644
--- a/src/lib/hooks/callout_manager.h
+++ b/src/lib/hooks/callout_manager.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -96,6 +96,36 @@ public:
/// - INT_MAX: used for server-registered callouts called after
/// user-registered callouts.
///
+/// Since Kea 1.3.0 release hook libraries can register callouts as control
+/// command handlers. Such handlers are associated with dynamically created
+/// hook points which names are created after command names. For example,
+/// if a command name is 'foo-bar', the name of the hook point to which
+/// callouts/command handlers are registered is '$foo_bar'. Prefixing the
+/// hook point name with the dollar sign eliminates potential conflicts
+/// between hook points dedicated to commands handling and other (fixed)
+/// hook points.
+///
+/// Prefixing hook names for command handlers with a dollar sign precludes
+/// auto registration of command handlers, i.e. hooks framework is unable
+/// to match hook points with names of functions implementing command
+/// handlers, because the dollar sign is not legal in C++ function names.
+/// This is intended because we want hook libraries to explicitly register
+/// commands handlers for supported commands and not rely on Kea to register
+/// hook points for them. Should we find use cases for auto registration of
+/// command handlers, we may modify the
+/// @ref ServerHooks::commandToHookName to use an encoding of hook
+/// point names for command handlers that would only contain characters
+/// allowed in function names.
+///
+/// The @ref CalloutManager::registerCommandHook has been added to allow for
+/// dynamically creating hook points for which command handlers are registered.
+/// This method is called from the @ref LibraryHandle::registerCommandCallout
+/// as a result of registering the command handlers by the hook library in
+/// its @c load() function. If the hook point for the given command already
+/// exists, this function doesn't do anything. The
+/// @ref LibraryHandle::registerCommandCallout can install callouts on this
+/// hook point.
+///
/// Note that the callout functions do not access the CalloutManager: instead,
/// they use a LibraryHandle object. This contains an internal pointer to
/// the CalloutManager, but provides a restricted interface. In that way,
@@ -184,9 +214,20 @@ public:
/// @throw NoSuchHook Given index does not correspond to a valid hook.
bool calloutsPresent(int hook_index) const;
+ /// @brief Checks if control command handlers are present for the
+ /// specified command.
+ ///
+ /// @param command_name Command name for which handlers' presence should
+ /// be checked.
+ ///
+ /// @return true if there is a hook point associated with the specified
+ /// command and callouts/command handlers are installed for this hook
+ /// point, false otherwise.
+ bool commandHandlersPresent(const std::string& command_name) const;
+
/// @brief Calls the callouts for a given hook
///
- /// Iterates through the libray handles and calls the callouts associated
+ /// Iterates through the library handles and calls the callouts associated
/// with the given hook index.
///
/// @note This method invalidates the current library index set with
@@ -197,6 +238,34 @@ public:
/// current object being processed.
void callCallouts(int hook_index, CalloutHandle& callout_handle);
+ /// @brief Calls the callouts/command handlers for a given command name.
+ ///
+ /// Iterates through the library handles and calls the command handlers
+ /// associated with the given command. It expects that the hook point
+ /// for this command exists (with a name being a command_name prefixed
+ /// with a dollar sign and with hyphens replaced with underscores).
+ ///
+ /// @param command_name Command name for which handlers should be called.
+ /// @param callout_handle Reference to the CalloutHandle object for the
+ /// current object being processed.
+ ///
+ /// @throw NoSuchHook if the hook point for the specified command does
+ /// not exist.
+ void callCommandHandlers(const std::string& command_name,
+ CalloutHandle& callout_handle);
+
+ /// @brief Registers a hook point for the specified command name.
+ ///
+ /// If the hook point for such command already exists, this function
+ /// doesn't do anything. The registered hook point name is created
+ /// after command_name by prefixing it with a dollar sign and replacing
+ /// all hyphens with underscores, e.g. for the 'foo-bar' command the
+ /// following hook point name will be generated: '$foo_bar'.
+ ///
+ /// @param command_name Command name for which the hook point should be
+ /// registered.
+ void registerCommandHook(const std::string& command_name);
+
/// @brief Get current hook index
///
/// Made available during callCallouts, this is the index of the hook
diff --git a/src/lib/hooks/hooks_config.cc b/src/lib/hooks/hooks_config.cc
new file mode 100644
index 0000000000..ce0e268561
--- /dev/null
+++ b/src/lib/hooks/hooks_config.cc
@@ -0,0 +1,127 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <hooks/hooks_config.h>
+#include <hooks/hooks_manager.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+
+namespace isc {
+namespace hooks {
+
+void
+HooksConfig::verifyLibraries(const Element::Position& position) const {
+ // The code used to follow this logic:
+ //
+ // Check if the list of libraries has changed. If not, nothing is done
+ // - the command "DhcpN libreload" is required to reload the same
+ // libraries (this prevents needless reloads when anything else in the
+ // configuration is changed).
+ //
+ // We no longer rely on this. Parameters can change. And even if the
+ // parameters stay the same, they could point to files that could
+ // change. We can skip loading routines only if there were and there still
+ // are no libraries specified.
+ vector<string> current_libraries = HooksManager::getLibraryNames();
+ if (current_libraries.empty() && libraries_.empty()) {
+ return;
+ }
+
+ // Library list has changed, validate each of the libraries specified.
+ vector<string> lib_names = isc::hooks::extractNames(libraries_);
+ vector<string> error_libs = HooksManager::validateLibraries(lib_names);
+ if (!error_libs.empty()) {
+
+ // Construct the list of libraries in error for the message.
+ string error_list = error_libs[0];
+ for (size_t i = 1; i < error_libs.size(); ++i) {
+ error_list += (string(", ") + error_libs[i]);
+ }
+ isc_throw(InvalidHooksLibraries,
+ "hooks libraries failed to validate - "
+ "library or libraries in error are: "
+ << error_list << "(" << position << ")");
+ }
+}
+
+void
+HooksConfig::loadLibraries() const {
+ /// Commits the list of libraries to the configuration manager storage if
+ /// the list of libraries has changed.
+ /// @todo: Delete any stored CalloutHandles before reloading the
+ /// libraries
+ if (!HooksManager::loadLibraries(libraries_)) {
+ isc_throw(InvalidHooksLibraries,
+ "One or more hook libraries failed to load");
+ }
+}
+
+bool
+HooksConfig::equal(const HooksConfig& other) const {
+
+ /// @todo: This comparision assumes that the library order is not relevant,
+ /// so [ lib1, lib2 ] is equal to [ lib2, lib1 ]. However, this is not strictly
+ /// true, because callouts execution is called in other they're loaded. Therefore
+ /// changing the libraries order may change the server behavior.
+ ///
+ /// We don't have any libraries that are interacting (or would change their behavior
+ /// depending on the order in which their callouts are executed), so the code is
+ /// ok for now.
+ for (isc::hooks::HookLibsCollection::const_iterator this_it = libraries_.begin();
+ this_it != libraries_.end(); ++this_it) {
+ bool match = false;
+ for (isc::hooks::HookLibsCollection::const_iterator other_it =
+ other.libraries_.begin(); other_it != other.libraries_.end(); ++other_it) {
+ if (this_it->first != other_it->first) {
+ continue;
+ }
+ if (isNull(this_it->second) && isNull(other_it->second)) {
+ match = true;
+ break;
+ }
+ if (isNull(this_it->second) || isNull(other_it->second)) {
+ continue;
+ }
+ if (this_it->second->equals(*other_it->second)) {
+ match = true;
+ break;
+ }
+ }
+ // No match found for the particular hooks library so return false.
+ if (!match) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+ElementPtr
+HooksConfig::toElement() const {
+ // hooks-libraries is a list of maps
+ ElementPtr result = Element::createList();
+ // Iterate through libraries
+ for (HookLibsCollection::const_iterator hl = libraries_.begin();
+ hl != libraries_.end(); ++hl) {
+ // Entries are maps
+ ElementPtr map = Element::createMap();
+ // Set the library name
+ map->set("library", Element::create(hl->first));
+ // Set parameters (not set vs set empty map)
+ if (!isNull(hl->second)) {
+ map->set("parameters", hl->second);
+ }
+ // Push to the list
+ result->add(map);
+ }
+ return (result);
+}
+
+};
+};
diff --git a/src/lib/hooks/hooks_config.h b/src/lib/hooks/hooks_config.h
new file mode 100644
index 0000000000..837727e40e
--- /dev/null
+++ b/src/lib/hooks/hooks_config.h
@@ -0,0 +1,108 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HOOKS_CONFIG_H
+#define HOOKS_CONFIG_H
+
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <cc/cfg_to_element.h>
+#include <hooks/libinfo.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Exception thrown when a library failed to validate
+class InvalidHooksLibraries : public Exception {
+ public:
+ InvalidHooksLibraries(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Wrapper class that holds hooks libraries configuration
+///
+/// This was moved from HooksLibrariesParser
+///
+/// However, this class does more than just check the list of library names.
+/// It does two other things:
+/// # validate libraries
+/// # load libraries
+/// and it provides a toElement() method to unparse a configuration.
+///
+/// @todo add toElement() unit tests
+class HooksConfig : public isc::data::CfgToElement {
+public:
+ /// @brief Default constructor.
+ ///
+ HooksConfig() : libraries_() { }
+
+ /// @brief Adds additional hooks libraries.
+ ///
+ /// @param libname full filename with path to the library.
+ /// @param parameters map of parameters that configure the library.
+ void add(std::string libname, isc::data::ConstElementPtr parameters) {
+ libraries_.push_back(make_pair(libname, parameters));
+ }
+
+ /// @brief Provides access to the configured hooks libraries.
+ ///
+ /// @note The const reference returned is only valid as long as the
+ /// object that returned it.
+ const isc::hooks::HookLibsCollection& get() const {
+ return libraries_;
+ }
+
+ /// @brief Removes all configured hooks libraries.
+ void clear() {
+ libraries_.clear();
+ }
+
+ /// @brief Compares two Hooks Config classes for equality
+ ///
+ /// @param other other hooksconfig to compare with
+ bool equal(const HooksConfig& other) const;
+
+ /// @brief Verifies that libraries stored in libraries_ are valid.
+ ///
+ /// This method is a smart wrapper around @ref
+ /// isc::hooks::HooksManager::validateLibraries().
+ /// It tries to validate all the libraries stored in libraries_.
+ ///
+ /// @param position position of the hooks-library map for error reporting
+ /// @throw InvalidHooksLibraries if any issue is discovered.
+ void verifyLibraries(const isc::data::Element::Position& position) const;
+
+ /// @brief Commits hooks libraries configuration.
+ ///
+ /// This method calls necessary methods in HooksManager that will unload
+ /// any libraries that may be currently loaded and will load the actual
+ /// libraries. Providing that the specified libraries are valid and are
+ /// different to those already loaded, this method loads the new set of
+ /// libraries (and unloads the existing set).
+ ///
+ /// @throw InvalidHooksLibraries if the call to HooksManager fails.
+ void loadLibraries() const;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// Returns an element which must parse into the same object, i.e.
+ /// @code
+ /// for all valid config C parse(parse(C)->toElement()) == parse(C)
+ /// @endcode
+ ///
+ /// @return a pointer to a configuration which can be parsed into
+ /// the initial configuration object
+ isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief List of hooks libraries with their configuration parameters
+ isc::hooks::HookLibsCollection libraries_;
+};
+
+};
+};
+
+#endif // HOOKS_CONFIG_H
diff --git a/src/lib/hooks/hooks_log.cc b/src/lib/hooks/hooks_log.cc
index 0426364486..8e554b5166 100644
--- a/src/lib/hooks/hooks_log.cc
+++ b/src/lib/hooks/hooks_log.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
/// Defines the logger used by the Hooks
#include <hooks/hooks_log.h>
+#include <log/macros.h>
namespace isc {
namespace hooks {
@@ -15,6 +16,11 @@ isc::log::Logger hooks_logger("hooks");
isc::log::Logger callouts_logger("callouts");
+const int HOOKS_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
+const int HOOKS_DBG_CALLS = isc::log::DBGLVL_TRACE_BASIC_DATA;
+const int HOOKS_DBG_EXTENDED_CALLS = isc::log::DBGLVL_TRACE_DETAIL_DATA;
+
+
} // namespace hooks
} // namespace isc
diff --git a/src/lib/hooks/hooks_log.h b/src/lib/hooks/hooks_log.h
index beb020f6ca..6b567c9714 100644
--- a/src/lib/hooks/hooks_log.h
+++ b/src/lib/hooks/hooks_log.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -19,14 +19,14 @@ namespace hooks {
/// Note that higher numbers equate to more verbose (and detailed) output.
// The first level traces normal operations,
-const int HOOKS_DBG_TRACE = DBGLVL_TRACE_BASIC;
+extern const int HOOKS_DBG_TRACE;
// The next level traces each call to hook code.
-const int HOOKS_DBG_CALLS = DBGLVL_TRACE_BASIC_DATA;
+extern const int HOOKS_DBG_CALLS;
// Additional information on the calls. Report each call to a callout (even
// if there are multiple callouts on a hook) and each status return.
-const int HOOKS_DBG_EXTENDED_CALLS = DBGLVL_TRACE_DETAIL_DATA;
+extern const int HOOKS_DBG_EXTENDED_CALLS;
/// @brief Hooks Logger
diff --git a/src/lib/hooks/hooks_manager.cc b/src/lib/hooks/hooks_manager.cc
index 90d7dc1434..b4702baff0 100644
--- a/src/lib/hooks/hooks_manager.cc
+++ b/src/lib/hooks/hooks_manager.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -48,19 +48,44 @@ HooksManager::calloutsPresent(int index) {
return (getHooksManager().calloutsPresentInternal(index));
}
+bool
+HooksManager::commandHandlersPresentInternal(const std::string& command_name) {
+ conditionallyInitialize();
+ return (callout_manager_->commandHandlersPresent(command_name));
+}
+
+bool
+HooksManager::commandHandlersPresent(const std::string& command_name) {
+ return (getHooksManager().commandHandlersPresentInternal(command_name));
+}
+
// Call the callouts
void
HooksManager::callCalloutsInternal(int index, CalloutHandle& handle) {
conditionallyInitialize();
- return (callout_manager_->callCallouts(index, handle));
+ callout_manager_->callCallouts(index, handle);
}
void
HooksManager::callCallouts(int index, CalloutHandle& handle) {
- return (getHooksManager().callCalloutsInternal(index, handle));
+ getHooksManager().callCalloutsInternal(index, handle);
}
+void
+HooksManager::callCommandHandlersInternal(const std::string& command_name,
+ CalloutHandle& handle) {
+ conditionallyInitialize();
+ callout_manager_->callCommandHandlers(command_name, handle);
+}
+
+void
+HooksManager::callCommandHandlers(const std::string& command_name,
+ CalloutHandle& handle) {
+ getHooksManager().callCommandHandlersInternal(command_name, handle);
+}
+
+
// Load the libraries. This will delete the previously-loaded libraries
// (if present) and load new ones.
@@ -199,5 +224,11 @@ HooksManager::validateLibraries(const std::vector<std::string>& libraries) {
return (LibraryManagerCollection::validateLibraries(libraries));
}
+// Shared callout manager
+boost::shared_ptr<CalloutManager>&
+HooksManager::getSharedCalloutManager() {
+ return (getHooksManager().shared_callout_manager_);
+}
+
} // namespace util
} // namespace isc
diff --git a/src/lib/hooks/hooks_manager.h b/src/lib/hooks/hooks_manager.h
index b6080be8eb..3eae0a95fb 100644
--- a/src/lib/hooks/hooks_manager.h
+++ b/src/lib/hooks/hooks_manager.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -90,6 +90,17 @@ public:
/// @throw NoSuchHook Given index does not correspond to a valid hook.
static bool calloutsPresent(int index);
+ /// @brief Checks if control command handlers are present for the
+ /// specified command.
+ ///
+ /// @param command_name Command name for which handlers' presence should
+ /// be checked.
+ ///
+ /// @return true if there is a hook point associated with the specified
+ /// command and callouts/command handlers are installed for this hook
+ /// point, false otherwise.
+ static bool commandHandlersPresent(const std::string& command_name);
+
/// @brief Calls the callouts for a given hook
///
/// Iterates through the library handles and calls the callouts associated
@@ -103,6 +114,22 @@ public:
/// object being processed.
static void callCallouts(int index, CalloutHandle& handle);
+ /// @brief Calls the callouts/command handlers for a given command name.
+ ///
+ /// Iterates through the library handles and calls the command handlers
+ /// associated with the given command. It expects that the hook point
+ /// for this command exists (with a name being a command_name prefixed
+ /// with a dollar sign and with hyphens replaced with underscores).
+ ///
+ /// @param command_name Command name for which handlers should be called.
+ /// @param handle Reference to the CalloutHandle object for the current
+ /// object being processed.
+ ///
+ /// @throw NoSuchHook if the hook point for the specified command does
+ /// not exist.
+ static void callCommandHandlers(const std::string& command_name,
+ CalloutHandle& handle);
+
/// @brief Return pre-callouts library handle
///
/// Returns a library handle that can be used by the server to register
@@ -199,6 +226,14 @@ public:
static const int CONTEXT_CREATE = ServerHooks::CONTEXT_CREATE;
static const int CONTEXT_DESTROY = ServerHooks::CONTEXT_DESTROY;
+ /// @brief Return the shared callout manager
+ ///
+ /// Declared as static as other methods but only one for the
+ /// singleton will be created.
+ ///
+ /// @return A reference to the shared callout manager
+ static boost::shared_ptr<CalloutManager>& getSharedCalloutManager();
+
private:
/// @brief Constructor
@@ -245,6 +280,17 @@ private:
/// @throw NoSuchHook Given index does not correspond to a valid hook.
bool calloutsPresentInternal(int index);
+ /// @brief Checks if control command handlers are present for the
+ /// specified command.
+ ///
+ /// @param command_name Command name for which handlers' presence should
+ /// be checked.
+ ///
+ /// @return true if there is a hook point associated with the specified
+ /// command and callouts/command handlers are installed for this hook
+ /// point, false otherwise.
+ bool commandHandlersPresentInternal(const std::string& command_name);
+
/// @brief Calls the callouts for a given hook
///
/// @param index Index of the hook to call.
@@ -252,6 +298,17 @@ private:
/// object being processed.
void callCalloutsInternal(int index, CalloutHandle& handle);
+ /// @brief Calls the callouts/command handlers for a given command name.
+ ///
+ /// @param command_name Command name for which handlers should be called.
+ /// @param handle Reference to the CalloutHandle object for the current
+ /// object being processed.
+ ///
+ /// @throw NoSuchHook if the hook point for the specified command does
+ /// not exist.
+ void callCommandHandlersInternal(const std::string& command_name,
+ CalloutHandle& handle);
+
/// @brief Return callout handle
///
/// @return Shared pointer to a CalloutHandle object.
@@ -312,6 +369,10 @@ private:
/// Callout manager for the set of library managers.
boost::shared_ptr<CalloutManager> callout_manager_;
+
+ /// Shared callout manager to survive library reloads.
+ boost::shared_ptr<CalloutManager> shared_callout_manager_;
+
};
} // namespace util
diff --git a/src/lib/hooks/hooks_messages.mes b/src/lib/hooks/hooks_messages.mes
index b4fc5a6d13..c1440e71d3 100644
--- a/src/lib/hooks/hooks_messages.mes
+++ b/src/lib/hooks/hooks_messages.mes
@@ -47,7 +47,7 @@ index of the library (in the list of loaded libraries) that registered
it and the address of the callout. The error is otherwise ignored.
The error message includes the callout execution time in milliseconds.
-% HOOKS_CALLOUT_EXCEPTION exception thrown by callout on hook %1 registered by library with index %2 (callout address %3): %4 (callout duration: $5)
+% HOOKS_CALLOUT_EXCEPTION exception thrown by callout on hook %1 registered by library with index %2 (callout address %3): %4 (callout duration: %5)
If a callout throws an exception when called, this error message is
issued. It identifies the hook to which the callout is attached, the
index of the library (in the list of loaded libraries) that registered
diff --git a/src/lib/hooks/hooks_parser.cc b/src/lib/hooks/hooks_parser.cc
new file mode 100644
index 0000000000..cfd4a7e8f5
--- /dev/null
+++ b/src/lib/hooks/hooks_parser.cc
@@ -0,0 +1,110 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <cc/dhcp_config_error.h>
+#include <hooks/hooks_parser.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <util/strutil.h>
+#include <vector>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::dhcp;
+
+namespace isc {
+namespace hooks {
+
+// @todo use the flat style, split into list and item
+
+void
+HooksLibrariesParser::parse(HooksConfig& libraries, ConstElementPtr value) {
+ // Initialize.
+ libraries.clear();
+
+ if (!value) {
+ isc_throw(DhcpConfigError, "Tried to parse null hooks libraries");
+ }
+
+ // This is the new syntax. Iterate through it and get each map.
+ BOOST_FOREACH(ConstElementPtr library_entry, value->listValue()) {
+ ConstElementPtr parameters;
+
+ // Is it a map?
+ if (library_entry->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "hooks library configuration error:"
+ " one or more entries in the hooks-libraries list is not"
+ " a map (" << library_entry->getPosition() << ")");
+ }
+
+ // Iterate through each element in the map. We check
+ // whether we have found a library element.
+ bool lib_found = false;
+
+ string libname = "";
+
+ // Let's explicitly reset the parameters, so we won't cover old
+ // values from the previous loop round.
+ parameters.reset();
+
+ BOOST_FOREACH(auto entry_item, library_entry->mapValue()) {
+ if (entry_item.first == "library") {
+ if (entry_item.second->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "hooks library configuration"
+ " error: value of 'library' element is not a string"
+ " giving the path to a hooks library (" <<
+ entry_item.second->getPosition() << ")");
+ }
+
+ // Get the name of the library and add it to the list after
+ // removing quotes.
+ libname = (entry_item.second)->stringValue();
+
+ // Remove leading/trailing quotes and any leading/trailing
+ // spaces.
+ boost::erase_all(libname, "\"");
+ libname = isc::util::str::trim(libname);
+ if (libname.empty()) {
+ isc_throw(DhcpConfigError, "hooks library configuration"
+ " error: value of 'library' element must not be"
+ " blank (" <<
+ entry_item.second->getPosition() << ")");
+ }
+
+ // Note we have found the library name.
+ lib_found = true;
+ continue;
+ }
+
+ // If there are parameters, let's remember them.
+ if (entry_item.first == "parameters") {
+ parameters = entry_item.second;
+ continue;
+ }
+
+ // For all other parameters we will throw.
+ isc_throw(DhcpConfigError, "unknown hooks library parameter: "
+ << entry_item.first << "("
+ << library_entry->getPosition() << ")");
+ }
+
+ if (! lib_found) {
+ isc_throw(DhcpConfigError, "hooks library configuration error:"
+ " one or more hooks-libraries elements are missing the"
+ " name of the library" <<
+ " (" << library_entry->getPosition() << ")");
+ }
+
+ libraries.add(libname, parameters);
+ }
+}
+
+}
+}
diff --git a/src/lib/hooks/hooks_parser.h b/src/lib/hooks/hooks_parser.h
new file mode 100644
index 0000000000..4bec9a6aff
--- /dev/null
+++ b/src/lib/hooks/hooks_parser.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HOOKS_PARSER_H
+#define HOOKS_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <hooks/hooks_config.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Parser for hooks library list
+///
+/// This parser handles the list of hooks libraries. This is an optional list,
+/// which may be empty, and is encapsulated into a @ref HooksConfig object.
+class HooksLibrariesParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Parses parameters value
+ ///
+ /// Parses configuration entry (list of parameters) and adds each element
+ /// to the hooks libraries list. The method also checks whether the
+ /// list of libraries is the same as that already loaded. If not, it
+ /// checks each of the libraries in the list for validity (they exist and
+ /// have a "version" function that returns the correct value).
+ ///
+ /// The syntax for specifying hooks libraries allow for library-specific
+ /// parameters to be specified along with the library, e.g.
+ ///
+ /// @code
+ /// "hooks-libraries": [
+ /// {
+ /// "library": "hook-lib-1.so",
+ /// "parameters": {
+ /// "alpha": "a string",
+ /// "beta": 42
+ /// }
+ /// },
+ /// :
+ /// ]
+ /// @endcode
+ ///
+ /// The parsing code only checks that:
+ ///
+ /// -# Each element in the hooks-libraries list is a map
+ /// -# The map contains an element "library" whose value is a not blank string
+ /// -# That there is an optional 'parameters' element.
+ /// -# That there are no other element.
+ ///
+ /// This method stores parsed libraries in libraries.
+ ///
+ /// @param libraries parsed libraries information will be stored here
+ /// @param value pointer to the content to be parsed
+ void parse(HooksConfig& libraries, isc::data::ConstElementPtr value);
+};
+
+}; // namespace isc::hooks
+}; // namespace isc
+
+#endif
diff --git a/src/lib/hooks/hooks_user.dox b/src/lib/hooks/hooks_user.dox
index e3b081d6bb..0d094b3e6a 100644
--- a/src/lib/hooks/hooks_user.dox
+++ b/src/lib/hooks/hooks_user.dox
@@ -636,10 +636,10 @@ modify the server's behavior by taking responsibility for processing
the DHCP message at certain stages and instructing the server to skip
the default processing for that stage. Thus, hooks libraries play an
important role in the DHCP server operation and, depending on their
-purpose, they may have high complexity, which increases likehood of the
+purpose, they may have high complexity, which increases likelihood of the
defects in the libraries.
-All hooks libraries should use Kea logging system to faciliate diagnostics
+All hooks libraries should use Kea logging system to facilitate diagnostics
of the defects in the libraries and issues with the DHCP server's operation.
Even if the issue doesn't originate in the hooks library itself, the use
of the library may uncover issues in the Kea code that only
@@ -724,7 +724,7 @@ the debug severity) should use different debug levels for the
messages of different importance or having different performance
requirements to generate the log message. For example, generation of
a log message, which prints full details of a packet, usually requires
-more CPU bandwith than the generation of the message which only prints
+more CPU bandwidth than the generation of the message which only prints
the packet type and length. Thus, the former should be logged at
lower debug level (see @ref logSeverity for details of using
various debug levels using "dbglevel" parameter).
diff --git a/src/lib/hooks/library_handle.cc b/src/lib/hooks/library_handle.cc
index 4aebf6fe80..5707dbae52 100644
--- a/src/lib/hooks/library_handle.cc
+++ b/src/lib/hooks/library_handle.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,8 @@
#include <hooks/library_handle.h>
#include <hooks/hooks_manager.h>
+#include <iostream>
+
namespace isc {
namespace hooks {
@@ -33,6 +35,16 @@ LibraryHandle::registerCallout(const std::string& name, CalloutPtr callout) {
}
}
+void
+LibraryHandle::registerCommandCallout(const std::string& command_name,
+ CalloutPtr callout) {
+ // Register hook point for this command, if one doesn't exist.
+ callout_manager_->registerCommandHook(command_name);
+ // Register the command handler as a callout.
+ registerCallout(ServerHooks::commandToHookName(command_name), callout);
+}
+
+
bool
LibraryHandle::deregisterCallout(const std::string& name, CalloutPtr callout) {
int saved_index = callout_manager_->getLibraryIndex();
diff --git a/src/lib/hooks/library_handle.h b/src/lib/hooks/library_handle.h
index a94ad66fdf..5dfad2a909 100644
--- a/src/lib/hooks/library_handle.h
+++ b/src/lib/hooks/library_handle.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -40,6 +40,22 @@ extern "C" {
/// called, the CalloutManager uses that information to set the "current
/// library": the registration functions only operator on data whose
/// associated library is equal to the "current library".)
+///
+/// As of Kea 1.3.0 release, the @ref LibraryHandle can be used by the hook
+/// libraries to install control command handlers and dynamically register
+/// hook points with which the handlers are associated. For example, if the
+/// hook library supports control-command 'foo-bar' it should register its
+/// handler similarly to this:
+/// @code
+/// int load(LibraryHandle& libhandle) {
+/// libhandle.registerCommandCallout("foo-bar", foo_bar_handler);
+/// return (0);
+/// }
+/// @endcode
+///
+/// which will result in automatic creation of the hook point for the command
+/// (if one doesn't exist) and associating the callout 'foo_bar_handler' with
+/// this hook point as a handler for the command.
class LibraryHandle {
public:
@@ -79,6 +95,17 @@ public:
/// is of the wrong size.
void registerCallout(const std::string& name, CalloutPtr callout);
+ /// @brief Register control command handler
+ ///
+ /// Registers control command handler by creating a hook point for this
+ /// command (if it doesn't exist) and associating the callout as a command
+ /// handler. It is possible to register multiple command handlers for the
+ /// same control command because command handlers are implemented as callouts.
+ ///
+ /// @param command_name Command name for which handler should be installed.
+ /// @param callout Pointer to the command handler implemented as a callout.
+ void registerCommandCallout(const std::string& command_name, CalloutPtr callout);
+
/// @brief De-Register a callout on a hook
///
/// Searches through the functions registered by the current library with
@@ -135,10 +162,10 @@ public:
/// }
///]
///
- /// The first library has no parameters, so regardles of the name
+ /// The first library has no parameters, so regardless of the name
/// specified, for that library getParameter will always return NULL.
///
- /// For the second paramter, depending the following calls will return:
+ /// For the second parameter, depending the following calls will return:
/// - x = getParameter("mail") will return instance of
/// isc::data::StringElement. The content can be accessed with
/// x->stringValue() and will return std::string.
diff --git a/src/lib/hooks/library_manager.cc b/src/lib/hooks/library_manager.cc
index dfc25fa9af..ab10837da9 100644
--- a/src/lib/hooks/library_manager.cc
+++ b/src/lib/hooks/library_manager.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -33,7 +33,8 @@ namespace hooks {
LibraryManager::LibraryManager(const std::string& name, int index,
const boost::shared_ptr<CalloutManager>& manager)
: dl_handle_(NULL), index_(index), manager_(manager),
- library_name_(name)
+ library_name_(name),
+ server_hooks_(ServerHooks::getServerHooksPtr())
{
if (!manager) {
isc_throw(NoCalloutManager, "must specify a CalloutManager when "
diff --git a/src/lib/hooks/library_manager.h b/src/lib/hooks/library_manager.h
index 03f3ddfd7a..5f6bc4cac7 100644
--- a/src/lib/hooks/library_manager.h
+++ b/src/lib/hooks/library_manager.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,7 +8,7 @@
#define LIBRARY_MANAGER_H
#include <exceptions/exceptions.h>
-
+#include <hooks/server_hooks.h>
#include <boost/shared_ptr.hpp>
#include <string>
@@ -219,6 +219,8 @@ private:
///< Callout manager for registration
std::string library_name_; ///< Name of the library
+ ServerHooksPtr server_hooks_; ///< Stores a pointer to ServerHooks.
+
};
} // namespace hooks
diff --git a/src/lib/hooks/library_manager_collection.cc b/src/lib/hooks/library_manager_collection.cc
index ae2a37aa30..ea2daa843f 100644
--- a/src/lib/hooks/library_manager_collection.cc
+++ b/src/lib/hooks/library_manager_collection.cc
@@ -1,10 +1,11 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <hooks/callout_manager.h>
+#include <hooks/hooks_manager.h>
#include <hooks/library_manager.h>
#include <hooks/library_manager_collection.h>
@@ -52,17 +53,34 @@ LibraryManagerCollection::loadLibraries() {
// Unload libraries if any are loaded.
static_cast<void>(unloadLibraries());
- // Create the callout manager. A pointer to this is maintained by
- // each library. Note that the callout manager does not hold any memory
- // allocated by a library: although a library registers a callout (and so
- // causes the creation of an entry in the CalloutManager's callout list),
- // that creation is done by the CalloutManager itself. The CalloutManager
- // is created within the server.
+ // Access the callout manager, (re)creating it if required.
//
- // The upshot of this is that it is therefore safe for the CalloutManager
- // to be deleted after all associated libraries are deleted, hence this
- // link (LibraryManager -> CalloutManager) is safe.
- callout_manager_.reset(new CalloutManager(library_names_.size()));
+ // A pointer to the callout manager is maintained by each as well as by
+ // the HooksManager itself. Note that the callout manager does not hold any
+ // memory allocated by a library: although a library registers a callout
+ // (and so causes the creation of an entry in the CalloutManager's callout
+ // list), that creation is done by the CalloutManager itself. The
+ // CalloutManager is created within the server. The upshot of this is that
+ // it is therefore safe for the CalloutManager to be deleted after all
+ // associated libraries are deleted, hence this link (LibraryManager ->
+ // CalloutManager) is safe.
+ //
+ // If the list of libraries is not empty, re-create the callout manager.
+ // This deletes all callouts (including the pre-library and post-
+ // library) ones. It is up to the libraries to re-register their callouts.
+ // The pre-library and post-library callouts will also need to be
+ // re-registered.
+ //
+ // If the list of libraries stays empty (as in the case of a reconfiguration
+ // where the hooks-libraries clause was empty and is not changed), try
+ // to re-use the existing callout manager (so retaining registered pre-
+ // and post-library callouts).
+ if (library_names_.empty()) {
+ callout_manager_ = HooksManager::getSharedCalloutManager();
+ }
+ if (!library_names_.empty() || !callout_manager_) {
+ callout_manager_.reset(new CalloutManager(library_names_.size()));
+ }
// Now iterate through the libraries are load them one by one. We'll
for (size_t i = 0; i < library_names_.size(); ++i) {
diff --git a/src/lib/hooks/library_manager_collection.h b/src/lib/hooks/library_manager_collection.h
index fb3ce96c43..df52649ecb 100644
--- a/src/lib/hooks/library_manager_collection.h
+++ b/src/lib/hooks/library_manager_collection.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -98,7 +98,7 @@ public:
///
/// Returns a callout manager that can be used with this set of loaded
/// libraries (even if the number of loaded libraries is zero). This
- /// method may only be caslled after loadLibraries() has been called.
+ /// method may only be called after loadLibraries() has been called.
///
/// @return Pointer to a callout manager for this set of libraries.
///
@@ -139,7 +139,7 @@ public:
///
/// @param libraries List of libraries to validate
///
- /// @return Vector of libraries that faled to validate, or an empty vector
+ /// @return Vector of libraries that failed to validate, or an empty vector
/// if all validated.
static std::vector<std::string>
validateLibraries(const std::vector<std::string>& libraries);
diff --git a/src/lib/hooks/server_hooks.cc b/src/lib/hooks/server_hooks.cc
index ebd416998b..d7cfcdf901 100644
--- a/src/lib/hooks/server_hooks.cc
+++ b/src/lib/hooks/server_hooks.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#include <hooks/hooks_log.h>
#include <hooks/server_hooks.h>
+#include <algorithm>
#include <utility>
#include <vector>
@@ -46,10 +47,24 @@ ServerHooks::registerHook(const string& name) {
hooks_.insert(make_pair(name, index));
if (!result.second) {
+
+ // There's a problem with hook libraries that need to be linked with
+ // libdhcpsrv. For example host_cmds hook library requires host
+ // parser, so it needs to be linked with libdhcpsrv. However, when
+ // unit-tests are started, the hook points are not registered.
+ // When the library is loaded new hook points are registered.
+ // This causes issues in the hooks framework, especially when
+ // LibraryManager::unloadLibrary() iterates through all hooks
+ // and then calls deregisterAllCallouts. This method gets
+ // hook_index that is greater than number of elements in
+ // hook_vector_ and then we have a read past the array boundary.
+ /// @todo: See ticket 5251 and 5208 for details.
+ return (getIndex(name));
+
// New element was not inserted because an element with the same name
// already existed.
- isc_throw(DuplicateHook, "hook with name " << name <<
- " is already registered");
+ //isc_throw(DuplicateHook, "hook with name " << name <<
+ // " is already registered");
}
// Element was inserted, so add to the inverse hooks collection.
@@ -123,6 +138,13 @@ ServerHooks::getIndex(const string& name) const {
return (i->second);
}
+int
+ServerHooks::findIndex(const std::string& name) const {
+ // Get iterator to matching element.
+ auto i = hooks_.find(name);
+ return ((i == hooks_.end()) ? -1 : i->second);
+}
+
// Return vector of hook names. The names are not sorted - it is up to the
// caller to perform sorting if required.
@@ -142,10 +164,35 @@ ServerHooks::getHookNames() const {
ServerHooks&
ServerHooks::getServerHooks() {
- static ServerHooks hooks;
+ return (*getServerHooksPtr());
+}
+
+ServerHooksPtr
+ServerHooks::getServerHooksPtr() {
+ static ServerHooksPtr hooks(new ServerHooks());
return (hooks);
}
+std::string
+ServerHooks::commandToHookName(const std::string& command_name) {
+ // Prefix the command name with a dollar sign.
+ std::string hook_name = std::string("$") + command_name;
+ // Replace all hyphens with underscores.
+ std::replace(hook_name.begin(), hook_name.end(), '-', '_');
+ return (hook_name);
+}
+
+std::string
+ServerHooks::hookToCommandName(const std::string& hook_name) {
+ if (!hook_name.empty() && hook_name.front() == '$') {
+ std::string command_name = hook_name.substr(1);
+ std::replace(command_name.begin(), command_name.end(), '_', '-');
+ return (command_name);
+ }
+ return ("");
+}
+
+
-} // namespace util
+} // namespace hooks
} // namespace isc
diff --git a/src/lib/hooks/server_hooks.h b/src/lib/hooks/server_hooks.h
index 246cb83e61..9fc60bcdce 100644
--- a/src/lib/hooks/server_hooks.h
+++ b/src/lib/hooks/server_hooks.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
#include <exceptions/exceptions.h>
#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
#include <map>
#include <string>
@@ -37,6 +38,8 @@ public:
isc::Exception(file, line, what) {}
};
+class ServerHooks;
+typedef boost::shared_ptr<ServerHooks> ServerHooksPtr;
/// @brief Server hook collection
///
@@ -92,7 +95,7 @@ public:
/// Returns the name of a hook given the index. This is most likely to be
/// used in log messages.
///
- /// @param index Index of the hoold
+ /// @param index Index of the hook
///
/// @return Name of the hook.
///
@@ -110,6 +113,17 @@ public:
/// @throw NoSuchHook if the hook name is unknown to the caller.
int getIndex(const std::string& name) const;
+ /// @brief Find hook index
+ ///
+ /// Provides exception safe method of retrieving an index of the
+ /// specified hook.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook if the hook point exists, or -1 if the
+ /// hook point doesn't exist.
+ int findIndex(const std::string& name) const;
+
/// @brief Return number of hooks
///
/// Returns the total number of hooks registered.
@@ -133,6 +147,41 @@ public:
/// @return Reference to the global ServerHooks object.
static ServerHooks& getServerHooks();
+ /// @brief Returns pointer to ServerHooks object.
+ ///
+ /// @return Pointer to the global ServerHooks object.
+ static ServerHooksPtr getServerHooksPtr();
+
+ /// @brief Generates hook point name for the given control command name.
+ ///
+ /// This function is called to generate the name of the hook point
+ /// when the hook point is used to install command handlers for the
+ /// given control command.
+ ///
+ /// The name of the hook point is generated as follows:
+ /// - command name is prefixed with a dollar sign,
+ /// - all hyphens are replaced with underscores.
+ ///
+ /// For example, if the command_name is 'foo-bar', the resulting hook
+ /// point name will be '$foo_bar'.
+ ///
+ /// @param command_name Command name for which the hook point name is
+ /// to be generated.
+ ///
+ /// @return Hook point name, or an empty string if the command name
+ /// can't be converted to a hook name (e.g. when it lacks dollar sign).
+ static std::string commandToHookName(const std::string& command_name);
+
+ /// @brief Returns command name for a specified hook name.
+ ///
+ /// This function removes leading dollar sign and replaces underscores
+ /// with hyphens.
+ ///
+ /// @param hook_name Hook name for which command name should be returned.
+ ///
+ /// @return Command name.
+ static std::string hookToCommandName(const std::string& hook_name);
+
private:
/// @brief Constructor
///
diff --git a/src/lib/hooks/tests/.gitignore b/src/lib/hooks/tests/.gitignore
index 6fa0ec3e0c..c0fc13f4f9 100644
--- a/src/lib/hooks/tests/.gitignore
+++ b/src/lib/hooks/tests/.gitignore
@@ -1,4 +1,3 @@
/marker_file.h
/test_libraries.h
-
/run_unittests
diff --git a/src/lib/hooks/tests/callout_manager_unittest.cc b/src/lib/hooks/tests/callout_manager_unittest.cc
index 33357a2ae1..03da266ab2 100644
--- a/src/lib/hooks/tests/callout_manager_unittest.cc
+++ b/src/lib/hooks/tests/callout_manager_unittest.cc
@@ -867,6 +867,56 @@ TEST_F(CalloutManagerTest, LibraryHandlePrePostUserLibrary) {
EXPECT_EQ(154, callout_value_);
}
+// Test that control command handlers can be installed as callouts.
+
+TEST_F(CalloutManagerTest, LibraryHandleRegisterCommandHandler) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Simulate creation of the two hook libraries. Fist library implements two
+ // handlers for the control command 'command-one'. Second library implements
+ // two control command handlers: one for the 'command-one', another one for
+ // 'command-two'. Each of the handlers for the 'command-one' must be called
+ // and they must be called in the appropriate order. Command handler for
+ // 'command-two' should also be called.
+
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->getLibraryHandle().registerCommandCallout("command-one",
+ callout_one);
+ getCalloutManager()->getLibraryHandle().registerCommandCallout("command-one",
+ callout_four);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->getLibraryHandle().registerCommandCallout("command-one",
+ callout_two);
+ getCalloutManager()->getLibraryHandle().registerCommandCallout("command-two",
+ callout_three);
+
+ // Command handlers are installed for commands: 'command-one' and 'command-two'.
+ EXPECT_TRUE(getCalloutManager()->commandHandlersPresent("command-one"));
+ EXPECT_TRUE(getCalloutManager()->commandHandlersPresent("command-two"));
+ // There should be no handlers installed for 'command-three' and 'command-four'.
+ EXPECT_FALSE(getCalloutManager()->commandHandlersPresent("command-three"));
+ EXPECT_FALSE(getCalloutManager()->commandHandlersPresent("command-four"));
+
+ // Call handlers for 'command-one'. There should be three handlers called in
+ // the following order: 1, 4, 2.
+ callout_value_ = 0;
+ ASSERT_NO_THROW(getCalloutManager()->callCommandHandlers("command-one", handle));
+ EXPECT_EQ(142, callout_value_);
+
+ // There should be one handler invoked for the 'command-two'. This handler has
+ // index of 3.
+ callout_value_ = 0;
+ ASSERT_NO_THROW(getCalloutManager()->callCommandHandlers("command-two", handle));
+ EXPECT_EQ(3, callout_value_);
+
+ // An attempt to call handlers for the commands for which no hook points
+ // were created should result in exception.
+ EXPECT_THROW(getCalloutManager()->callCommandHandlers("command-three", handle),
+ NoSuchHook);
+ EXPECT_THROW(getCalloutManager()->callCommandHandlers("command-four", handle),
+ NoSuchHook);
+}
+
// The setting of the hook index is checked in the handles_unittest
// set of tests, as access restrictions mean it is not easily tested
// on its own.
diff --git a/src/lib/hooks/tests/common_test_class.h b/src/lib/hooks/tests/common_test_class.h
index 48251e41b1..5cfef3d578 100644
--- a/src/lib/hooks/tests/common_test_class.h
+++ b/src/lib/hooks/tests/common_test_class.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -125,6 +125,46 @@ public:
EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
}
+ /// @brief Call command handlers test.
+ ///
+ /// This test is similar to @c executeCallCallouts but it uses
+ /// @ref CalloutManager::callCommandHandlers to execute the command
+ /// handlers for the following commands: 'command-one' and 'command-two'.
+ ///
+ /// @param manager CalloutManager to use for the test
+ /// @param r1..r2, d1..d2 Data (dN) and expected results (rN).
+ void executeCallCommandHandlers(
+ const boost::shared_ptr<isc::hooks::CalloutManager>& manager,
+ int d1, int r1, int d2, int r2) {
+ static const char* COMMON_TEXT = " command handler returned the wrong value";
+ static const char* RESULT = "result";
+
+ int result;
+
+ // Set up a callout handle for the calls.
+ isc::hooks::CalloutHandle handle(manager);
+
+ // Initialize the argument RESULT. This simplifies testing by
+ // eliminating the generation of an exception when we try the unload
+ // test. In that case, RESULT is unchanged.
+ handle.setArgument(RESULT, -1);
+
+ // Perform the first calculation: it should assign the data to the
+ // result.
+ handle.setArgument("data_1", d1);
+ manager->callCommandHandlers("command-one", handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(d1, result) << "command-one" << COMMON_TEXT;
+
+ // Perform the second calculation: it should multiply the data by 10
+ // and return in the result.
+ handle.setArgument("data_2", d2);
+ manager->callCommandHandlers("command-two", handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r2, result) << "command-two" << COMMON_TEXT;
+ }
+
+
/// Hook indexes. These are are made public for ease of reference.
int hookpt_one_index_;
int hookpt_two_index_;
diff --git a/src/lib/hooks/tests/full_callout_library.cc b/src/lib/hooks/tests/full_callout_library.cc
index 8aef81e187..1846592e15 100644
--- a/src/lib/hooks/tests/full_callout_library.cc
+++ b/src/lib/hooks/tests/full_callout_library.cc
@@ -101,6 +101,38 @@ hook_nonstandard_three(CalloutHandle& handle) {
return (0);
}
+// First command handler assigns data to a result.
+
+static int
+command_handler_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result = data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second command handler multiples the result by data by 10.
+
+static int
+command_handler_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data * 10;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
// Framework functions
int
@@ -118,6 +150,10 @@ load(LibraryHandle& handle) {
handle.registerCallout("hookpt_two", hook_nonstandard_two);
handle.registerCallout("hookpt_three", hook_nonstandard_three);
+ // Register command_handler_one as control command handler.
+ handle.registerCommandCallout("command-one", command_handler_one);
+ handle.registerCommandCallout("command-two", command_handler_two);
+
return (0);
}
diff --git a/src/lib/hooks/tests/hooks_manager_unittest.cc b/src/lib/hooks/tests/hooks_manager_unittest.cc
index 9d783d75e0..55e8138b9d 100644
--- a/src/lib/hooks/tests/hooks_manager_unittest.cc
+++ b/src/lib/hooks/tests/hooks_manager_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -40,9 +40,10 @@ public:
/// @brief Destructor
///
- /// Unload all libraries.
+ /// Unload all libraries and reset the shared manager.
~HooksManagerTest() {
HooksManager::unloadLibraries();
+ HooksManager::getSharedCalloutManager().reset();
}
@@ -92,6 +93,42 @@ public:
EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
}
+ /// @brief Call command handlers test.
+ ///
+ /// This test is similar to @c executeCallCallouts but it uses
+ /// @ref HooksManager::callCommandHandlers to execute the command
+ /// handlers for the following commands: 'command-one' and 'command-two'.
+ ///
+ /// @param r1..r2, d1..d2 Data (dN) and expected results (rN).
+ void executeCallCommandHandlers(int d1, int r1, int d2, int r2) {
+ static const char* COMMON_TEXT = " command handler returned the wrong value";
+ static const char* RESULT = "result";
+
+ int result;
+
+ // Set up a callout handle for the calls.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // Initialize the argument RESULT. This simplifies testing by
+ // eliminating the generation of an exception when we try the unload
+ // test. In that case, RESULT is unchanged.
+ handle->setArgument(RESULT, -1);
+
+ // Perform the first calculation: it should assign the data to the
+ // result.
+ handle->setArgument("data_1", d1);
+ HooksManager::callCommandHandlers("command-one", *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(d1, result) << "command-one" << COMMON_TEXT;
+
+ // Perform the second calculation: it should multiply the data by 10
+ // and return in the result.
+ handle->setArgument("data_2", d2);
+ HooksManager::callCommandHandlers("command-two", *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r2, result) << "command-two" << COMMON_TEXT;
+ }
+
private:
/// To avoid unused variable errors
std::string dummy(int i) {
@@ -137,6 +174,12 @@ TEST_F(HooksManagerTest, LoadLibraries) {
executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
}
+ // r2 = 5 * 7 * 10
+ {
+ SCOPED_TRACE("Calculation using command handlers");
+ executeCallCommandHandlers(5, 5, 7, 350);
+ }
+
// Try unloading the libraries.
EXPECT_NO_THROW(HooksManager::unloadLibraries());
@@ -391,6 +434,144 @@ TEST_F(HooksManagerTest, PrePostCalloutTest) {
EXPECT_EQ(-15, result);
}
+// Test with a shared manager the pre- and post- callout functions survive
+// a reload
+
+TEST_F(HooksManagerTest, PrePostCalloutShared) {
+
+ HookLibsCollection library_names;
+
+ // Initialize the shared manager.
+ HooksManager::getSharedCalloutManager().reset(new CalloutManager(0));
+
+ // Load the pre- and post- callouts.
+ HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPreCallout);
+ HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPostCallout);
+
+ // With the pre- and post- callouts above, the result expected is
+ //
+ // 1027 * 2
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ int result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2054, result);
+
+ // ... and check that the pre- and post- callout functions survive a
+ // reload.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ handle = HooksManager::createCalloutHandle();
+
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ // Expect same value i.e. 1027 * 2
+ result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2054, result);
+}
+
+// Test with a shared manager the pre- and post- callout functions survive
+// a reload but not with a not empty list of libraries
+
+TEST_F(HooksManagerTest, PrePostCalloutSharedNotEmpty) {
+
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Initialize the shared manager.
+ HooksManager::getSharedCalloutManager().reset(new CalloutManager(0));
+
+ // Load the pre- and post- callouts.
+ HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPreCallout);
+ HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPostCallout);
+
+ // With the pre- and post- callouts above, the result expected is
+ //
+ // 1027 * 2
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ int result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2054, result);
+
+ // ... and check that the pre- and post- callout functions don't survive a
+ // reload with a not empty list of libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ handle = HooksManager::createCalloutHandle();
+
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ // Expect result - data_2
+ result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(-15, result);
+}
+
+// Test with a shared manager the pre- and post- callout functions don't
+// survive a reload if the shared manager is initialized too late.
+
+TEST_F(HooksManagerTest, PrePostCalloutSharedTooLate) {
+
+ HookLibsCollection library_names;
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Initialize the shared manager (after loadLibraries so too late)
+ HooksManager::getSharedCalloutManager().reset(new CalloutManager(0));
+
+ // Load the pre- and post- callouts.
+ HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPreCallout);
+ HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPostCallout);
+
+ // With the pre- and post- callouts above, the result expected is
+ //
+ // 1027 * 2
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ int result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2054, result);
+
+ // ... and check that the pre- and post- callout functions don't survive a
+ // reload.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ handle = HooksManager::createCalloutHandle();
+
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ // Expect no change so result = 0
+ result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(0, result);
+}
+
// Check that everything works even with no libraries loaded. First that
// calloutsPresent() always returns false.
@@ -399,6 +580,8 @@ TEST_F(HooksManagerTest, NoLibrariesCalloutsPresent) {
EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_one_index_));
EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_two_index_));
EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(HooksManager::commandHandlersPresent("command-one"));
+ EXPECT_FALSE(HooksManager::commandHandlersPresent("command-two"));
}
TEST_F(HooksManagerTest, NoLibrariesCallCallouts) {
@@ -425,8 +608,13 @@ TEST_F(HooksManagerTest, RegisterHooks) {
EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
EXPECT_EQ(3, HooksManager::registerHook(string("beta")));
EXPECT_EQ(4, HooksManager::registerHook(string("gamma")));
- EXPECT_THROW(static_cast<void>(HooksManager::registerHook(string("alpha"))),
- DuplicateHook);
+
+
+ // The code used to throw, but it now allows to register the same
+ // hook several times. It simply returns existing index.
+ //EXPECT_THROW(static_cast<void>(HooksManager::registerHook(string("alpha"))),
+ // DuplicateHook);
+ EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
// ... an check the hooks are as we expect.
EXPECT_EQ(5, ServerHooks::getServerHooks().getCount());
@@ -542,7 +730,7 @@ TEST_F(HooksManagerTest, validateLibraries) {
// This test verifies that the specified parameters are accessed properly.
TEST_F(HooksManagerTest, LibraryParameters) {
- // Prepare paramters for the callout parameters library.
+ // Prepare parameters for the callout parameters library.
ElementPtr params = Element::createMap();
params->set("svalue", Element::create("string value"));
params->set("ivalue", Element::create(42));
diff --git a/src/lib/hooks/tests/library_manager_unittest.cc b/src/lib/hooks/tests/library_manager_unittest.cc
index 367ceb9430..b97e514638 100644
--- a/src/lib/hooks/tests/library_manager_unittest.cc
+++ b/src/lib/hooks/tests/library_manager_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -93,6 +93,17 @@ public:
r1, d2, r2, d3, r3);
}
+ /// @brief Call command handlers test.
+ ///
+ /// A wrapper around the method of the same name in the HooksCommonTestClass
+ /// object, this passes this class's CalloutManager to that method.
+ ///
+ /// @param r1..r2, d1..d2 Values and intermediate values expected.
+ void executeCallCommandHandlers(int d1, int r1, int d2, int r2) {
+ HooksCommonTestClass::executeCallCommandHandlers(callout_manager_,
+ d1, r1, d2, r2);
+ }
+
/// Callout manager used for the test.
boost::shared_ptr<CalloutManager> callout_manager_;
};
@@ -128,6 +139,14 @@ public:
};
+// Check that LibraryManager constructor requires a not null manager
+
+TEST_F(LibraryManagerTest, NullManager) {
+ boost::shared_ptr<CalloutManager> null_manager;
+ EXPECT_THROW(PublicLibraryManager(std::string("foo"), 0, null_manager),
+ NoCalloutManager);
+}
+
// Check that openLibrary() reports an error when it can't find the specified
// library.
@@ -263,6 +282,8 @@ TEST_F(LibraryManagerTest, CheckLoadCalled) {
EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two"));
EXPECT_FALSE(callout_manager_->calloutsPresent(
ServerHooks::CONTEXT_DESTROY));
@@ -273,6 +294,8 @@ TEST_F(LibraryManagerTest, CheckLoadCalled) {
EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_));
EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-two"));
EXPECT_FALSE(callout_manager_->calloutsPresent(
ServerHooks::CONTEXT_DESTROY));
@@ -282,6 +305,11 @@ TEST_F(LibraryManagerTest, CheckLoadCalled) {
// r3 = (5 * d1 + d2) * d3
executeCallCallouts(5, 5, 25, 7, 32, 10, 320);
+ // Execute command handlers for 'command-one' and 'command-two'.
+ //
+ // r2 = d1 * d2 * 10;
+ executeCallCommandHandlers(5, 5, 7, 350);
+
// Tidy up
EXPECT_TRUE(lib_manager.closeLibrary());
}
@@ -406,7 +434,7 @@ TEST_F(LibraryManagerTest, LibUnload) {
// Load the only library, specifying the index of 0 as it's the only
// library. This should load all callouts.
- PublicLibraryManager lib_manager(std::string(FULL_CALLOUT_LIBRARY),
+ PublicLibraryManager lib_manager(std::string(LOAD_CALLOUT_LIBRARY),
0, callout_manager_);
EXPECT_TRUE(lib_manager.openLibrary());
@@ -417,18 +445,24 @@ TEST_F(LibraryManagerTest, LibUnload) {
EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two"));
// Load the single standard callout and check it is registered correctly.
EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two"));
// Call the load function to load the other callouts.
EXPECT_TRUE(lib_manager.runLoad());
EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_));
EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-two"));
// Unload the library and check that the callouts have been removed from
// the CalloutManager.
@@ -436,6 +470,8 @@ TEST_F(LibraryManagerTest, LibUnload) {
EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two"));
}
// Now come the loadLibrary() tests that make use of all the methods tested
@@ -572,7 +608,7 @@ TEST_F(LibraryManagerTest, libraryLoggerSetup) {
EXPECT_TRUE(lib_manager.loadLibrary());
// After loading the library, the global logging dictionary should
- // contain log messages registerd for this library.
+ // contain log messages registered for this library.
const MessageDictionaryPtr& dict = MessageDictionary::globalDictionary();
EXPECT_EQ("basic callout load %1", dict->getText("BCL_LOAD_START"));
EXPECT_EQ("basic callout load end", dict->getText("BCL_LOAD_END"));
diff --git a/src/lib/hooks/tests/load_callout_library.cc b/src/lib/hooks/tests/load_callout_library.cc
index 5b96f32166..613ec2c79f 100644
--- a/src/lib/hooks/tests/load_callout_library.cc
+++ b/src/lib/hooks/tests/load_callout_library.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -77,7 +77,7 @@ hook_nonstandard_two(CalloutHandle& handle) {
return (0);
}
-// Final callout adds "data_3" to the result.
+// Third callout adds "data_3" to the result.
static int
hook_nonstandard_three(CalloutHandle& handle) {
@@ -93,6 +93,38 @@ hook_nonstandard_three(CalloutHandle& handle) {
return (0);
}
+// First command handler assigns data to a result.
+
+static int
+command_handler_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result = data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second command handler multiples the result by data by 10.
+
+static int
+command_handler_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data * 10;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
// Framework functions
int
@@ -109,6 +141,10 @@ int load(LibraryHandle& handle) {
handle.registerCallout("hookpt_two", hook_nonstandard_two);
handle.registerCallout("hookpt_three", hook_nonstandard_three);
+ // Register command_handler_one as control command handler.
+ handle.registerCommandCallout("command-one", command_handler_one);
+ handle.registerCommandCallout("command-two", command_handler_two);
+
return (0);
}
diff --git a/src/lib/hooks/tests/server_hooks_unittest.cc b/src/lib/hooks/tests/server_hooks_unittest.cc
index d66966af09..6dd6e38d0f 100644
--- a/src/lib/hooks/tests/server_hooks_unittest.cc
+++ b/src/lib/hooks/tests/server_hooks_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -28,7 +28,9 @@ TEST(ServerHooksTest, RegisterHooks) {
// There should be two hooks already registered, with indexes 0 and 1.
EXPECT_EQ(2, hooks.getCount());
EXPECT_EQ(0, hooks.getIndex("context_create"));
+ EXPECT_EQ(0, hooks.findIndex("context_create"));
EXPECT_EQ(1, hooks.getIndex("context_destroy"));
+ EXPECT_EQ(1, hooks.findIndex("context_destroy"));
// Check that the constants are as expected. (The intermediate variables
// are used because of problems with g++ 4.6.1/Ubuntu 11.10 when resolving
@@ -54,8 +56,9 @@ TEST(ServerHooksTest, RegisterHooks) {
}
// Check that duplicate names cannot be registered.
-
-TEST(ServerHooksTest, DuplicateHooks) {
+// This test has been updated. See #5251 for details. The old
+// code is retained in case we decide to get back to it.
+TEST(ServerHooksTest, DISABLED_OldDuplicateHooks) {
ServerHooks& hooks = ServerHooks::getServerHooks();
hooks.reset();
@@ -68,6 +71,29 @@ TEST(ServerHooksTest, DuplicateHooks) {
EXPECT_THROW(hooks.registerHook("gamma"), DuplicateHook);
}
+// Check that duplicate names are handled properly. The code used to throw,
+// but it now returns the existing index. See #5251 for details.
+TEST(ServerHooksTest, NewDuplicateHooks) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ int index = hooks.getIndex("context_create");
+
+ // Ensure we can duplicate one of the existing names.
+ // Instead of throwing, we just check that a reasonable
+ // index has been returned.
+ EXPECT_EQ(index, hooks.registerHook("context_create"));
+
+ // Check that mutiple attempts to register the same hook will return
+ // existing index.
+ int gamma = hooks.registerHook("gamma");
+ EXPECT_EQ(2, gamma);
+ EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+ EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+ EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+ EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+}
+
// Checks that we can get the name of the hooks.
TEST(ServerHooksTest, GetHookNames) {
@@ -153,6 +179,7 @@ TEST(ServerHooksTest, UnknownHookName) {
hooks.reset();
EXPECT_THROW(static_cast<void>(hooks.getIndex("unknown")), NoSuchHook);
+ EXPECT_EQ(-1, hooks.findIndex("unknown"));
}
// Check that the count of hooks is correct.
@@ -171,4 +198,23 @@ TEST(ServerHooksTest, HookCount) {
EXPECT_EQ(6, hooks.getCount());
}
+// Check that the hook name is correctly generated for a control command name
+// and vice versa.
+
+TEST(ServerHooksTest, CommandToHookName) {
+ EXPECT_EQ("$x_y_z", ServerHooks::commandToHookName("x-y-z"));
+ EXPECT_EQ("$foo_bar_foo", ServerHooks::commandToHookName("foo-bar_foo"));
+ EXPECT_EQ("$", ServerHooks::commandToHookName(""));
+}
+
+TEST(ServerHooksTest, HookToCommandName) {
+ // Underscores replaced by hyphens.
+ EXPECT_EQ("x-y-z", ServerHooks::hookToCommandName("$x_y_z"));
+ EXPECT_EQ("foo-bar-foo", ServerHooks::hookToCommandName("$foo_bar-foo"));
+ // Single dollar is converted to empty string.
+ EXPECT_TRUE(ServerHooks::hookToCommandName("$").empty());
+ // If no dollar, it is not a hook name. Return empty string.
+ EXPECT_TRUE(ServerHooks::hookToCommandName("abc").empty());
+}
+
} // Anonymous namespace
diff --git a/src/lib/http/.gitignore b/src/lib/http/.gitignore
new file mode 100644
index 0000000000..4aeda87246
--- /dev/null
+++ b/src/lib/http/.gitignore
@@ -0,0 +1,3 @@
+/http_messages.cc
+/http_messages.h
+/s-messages
diff --git a/src/lib/http/Makefile.am b/src/lib/http/Makefile.am
new file mode 100644
index 0000000000..588b138ad1
--- /dev/null
+++ b/src/lib/http/Makefile.am
@@ -0,0 +1,62 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Define rule to build logging source files from message file
+http_messages.h http_messages.cc: s-messages
+
+s-messages: http_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/http/http_messages.mes
+ touch $@
+
+# Tell automake that the message files are built as part of the build process
+# (so that they are built before the main library is built).
+BUILT_SOURCES = http_messages.h http_messages.cc
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = http_messages.mes
+
+# Get rid of generated message files on a clean
+CLEANFILES = *.gcno *.gcda http_messages.h http_messages.cc s-messages
+
+lib_LTLIBRARIES = libkea-http.la
+libkea_http_la_SOURCES = connection.cc connection.h
+libkea_http_la_SOURCES += connection_pool.cc connection_pool.h
+libkea_http_la_SOURCES += date_time.cc date_time.h
+libkea_http_la_SOURCES += http_log.cc http_log.h
+libkea_http_la_SOURCES += header_context.h
+libkea_http_la_SOURCES += http_acceptor.h
+libkea_http_la_SOURCES += http_types.h
+libkea_http_la_SOURCES += listener.cc listener.h
+libkea_http_la_SOURCES += post_request.cc post_request.h
+libkea_http_la_SOURCES += post_request_json.cc post_request_json.h
+libkea_http_la_SOURCES += request.cc request.h
+libkea_http_la_SOURCES += request_context.h
+libkea_http_la_SOURCES += request_parser.cc request_parser.h
+libkea_http_la_SOURCES += response.cc response.h
+libkea_http_la_SOURCES += response_creator.cc response_creator.h
+libkea_http_la_SOURCES += response_creator_factory.h
+libkea_http_la_SOURCES += response_json.cc response_json.h
+
+nodist_libkea_http_la_SOURCES = http_messages.cc http_messages.h
+
+libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_http_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_http_la_LDFLAGS = $(AM_LDFLAGS)
+libkea_http_la_LDFLAGS += -no-undefined -version-info 0:0:0
+
+libkea_http_la_LIBADD =
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_http_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_process_includedir = $(pkgincludedir)/http
+libkea_process_include_HEADERS = \
+ http_log.h
diff --git a/src/lib/http/connection.cc b/src/lib/http/connection.cc
new file mode 100644
index 0000000000..1e5f5cb2ec
--- /dev/null
+++ b/src/lib/http/connection.cc
@@ -0,0 +1,273 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <asiolink/asio_wrapper.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/http_log.h>
+#include <http/http_messages.h>
+#include <boost/bind.hpp>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace http {
+
+void
+HttpConnection::
+SocketCallback::operator()(boost::system::error_code ec, size_t length) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+ }
+ callback_(ec, length);
+}
+
+HttpConnection:: HttpConnection(asiolink::IOService& io_service,
+ HttpAcceptor& acceptor,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout)
+ : request_timer_(io_service),
+ request_timeout_(request_timeout),
+ socket_(io_service),
+ acceptor_(acceptor),
+ connection_pool_(connection_pool),
+ response_creator_(response_creator),
+ request_(response_creator_->createNewHttpRequest()),
+ parser_(new HttpRequestParser(*request_)),
+ acceptor_callback_(callback),
+ buf_() {
+ parser_->initModel();
+}
+
+HttpConnection::~HttpConnection() {
+ close();
+}
+
+void
+HttpConnection::close() {
+ request_timer_.cancel();
+ socket_.close();
+}
+
+void
+HttpConnection::stopThisConnection() {
+ try {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_CONNECTION_STOP)
+ .arg(getRemoteEndpointAddressAsText());
+ connection_pool_.stop(shared_from_this());
+ } catch (...) {
+ LOG_ERROR(http_logger, HTTP_CONNECTION_STOP_FAILED);
+ }
+}
+
+void
+HttpConnection::asyncAccept() {
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying boost functions make copies
+ // as needed.
+ HttpAcceptorCallback cb = boost::bind(&HttpConnection::acceptorCallback,
+ shared_from_this(),
+ boost::asio::placeholders::error);
+ try {
+ acceptor_.asyncAccept(socket_, cb);
+
+ } catch (const std::exception& ex) {
+ isc_throw(HttpConnectionError, "unable to start accepting TCP "
+ "connections: " << ex.what());
+ }
+}
+
+void
+HttpConnection::doRead() {
+ try {
+ TCPEndpoint endpoint;
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying boost functions make copies
+ // as needed.
+ SocketCallback cb(boost::bind(&HttpConnection::socketReadCallback,
+ shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ socket_.asyncReceive(static_cast<void*>(buf_.data()), buf_.size(),
+ 0, &endpoint, cb);
+
+ } catch (const std::exception& ex) {
+ stopThisConnection();
+ }
+}
+
+void
+HttpConnection::doWrite() {
+ try {
+ if (!output_buf_.empty()) {
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying boost functions make copies
+ // as needed.
+ SocketCallback cb(boost::bind(&HttpConnection::socketWriteCallback,
+ shared_from_this(),
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ socket_.asyncSend(output_buf_.data(),
+ output_buf_.length(),
+ cb);
+ } else {
+ stopThisConnection();
+ }
+ } catch (const std::exception& ex) {
+ stopThisConnection();
+ }
+}
+
+void
+HttpConnection::asyncSendResponse(const ConstHttpResponsePtr& response) {
+ output_buf_ = response->toString();
+ doWrite();
+}
+
+
+void
+HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
+ if (!acceptor_.isOpen()) {
+ return;
+ }
+
+ if (ec) {
+ stopThisConnection();
+ }
+
+ acceptor_callback_(ec);
+
+ if (!ec) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_REQUEST_RECEIVE_START)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(static_cast<unsigned>(request_timeout_/1000));
+ // Pass raw pointer rather than shared_ptr to this object,
+ // because IntervalTimer already passes shared pointer to the
+ // IntervalTimerImpl to make sure that the callback remains
+ // valid.
+ request_timer_.setup(boost::bind(&HttpConnection::requestTimeoutCallback,
+ this),
+ request_timeout_, IntervalTimer::ONE_SHOT);
+ doRead();
+ }
+}
+
+void
+HttpConnection::socketReadCallback(boost::system::error_code ec, size_t length) {
+ if (ec) {
+ // IO service has been stopped and the connection is probably
+ // going to be shutting down.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EWOULDBLOCK and EAGAIN are special cases. Everything else is
+ // treated as fatal error.
+ } else if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ stopThisConnection();
+
+ // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
+ // read something from the socket on the next attempt. Just make sure
+ // we don't try to read anything now in case there is any garbage
+ // passed in length.
+ } else {
+ length = 0;
+ }
+ }
+
+ if (length != 0) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ HTTP_DATA_RECEIVED)
+ .arg(length)
+ .arg(getRemoteEndpointAddressAsText());
+
+ std::string s(&buf_[0], buf_[0] + length);
+ parser_->postBuffer(static_cast<void*>(buf_.data()), length);
+ parser_->poll();
+ }
+
+ if (parser_->needData()) {
+ doRead();
+
+ } else {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_REQUEST_RECEIVED)
+ .arg(getRemoteEndpointAddressAsText());
+ try {
+ request_->finalize();
+ } catch (...) {
+ }
+
+ HttpResponsePtr response = response_creator_->createHttpResponse(request_);
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_RESPONSE_SEND)
+ .arg(response->toBriefString())
+ .arg(getRemoteEndpointAddressAsText());
+ asyncSendResponse(response);
+ }
+}
+
+void
+HttpConnection::socketWriteCallback(boost::system::error_code ec, size_t length) {
+ if (ec) {
+ // IO service has been stopped and the connection is probably
+ // going to be shutting down.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EWOULDBLOCK and EAGAIN are special cases. Everything else is
+ // treated as fatal error.
+ } else if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ stopThisConnection();
+
+ // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
+ // read something from the socket on the next attempt.
+ } else {
+ doWrite();
+ }
+ }
+
+ if (length <= output_buf_.size()) {
+ output_buf_.erase(0, length);
+ doWrite();
+
+ } else {
+ output_buf_.clear();
+ stopThisConnection();
+ }
+}
+
+void
+HttpConnection::requestTimeoutCallback() {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_REQUEST_TIMEOUT_OCCURRED)
+ .arg(getRemoteEndpointAddressAsText());
+ HttpResponsePtr response =
+ response_creator_->createStockHttpResponse(request_,
+ HttpStatusCode::REQUEST_TIMEOUT);
+ asyncSendResponse(response);
+}
+
+std::string
+HttpConnection::getRemoteEndpointAddressAsText() const {
+ try {
+ if (socket_.getASIOSocket().is_open()) {
+ return (socket_.getASIOSocket().remote_endpoint().address().to_string());
+ }
+ } catch (...) {
+ }
+ return ("(unknown address)");
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc
+
diff --git a/src/lib/http/connection.h b/src/lib/http/connection.h
new file mode 100644
index 0000000000..6305d55159
--- /dev/null
+++ b/src/lib/http/connection.h
@@ -0,0 +1,219 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_CONNECTION_H
+#define HTTP_CONNECTION_H
+
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <http/http_acceptor.h>
+#include <http/request_parser.h>
+#include <http/response_creator_factory.h>
+#include <boost/function.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/shared_ptr.hpp>
+#include <array>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic error reported within @ref HttpConnection class.
+class HttpConnectionError : public Exception {
+public:
+ HttpConnectionError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Forward declaration to the @ref HttpConnectionPool.
+///
+/// This declaration is needed because we don't include the header file
+/// declaring @ref HttpConnectionPool to avoid circular inclusion.
+class HttpConnectionPool;
+
+class HttpConnection;
+/// @brief Pointer to the @ref HttpConnection.
+typedef boost::shared_ptr<HttpConnection> HttpConnectionPtr;
+
+/// @brief Accepts and handles a single HTTP connection.
+class HttpConnection : public boost::enable_shared_from_this<HttpConnection> {
+private:
+
+ /// @brief Type of the function implementing a callback invoked by the
+ /// @c SocketCallback functor.
+ typedef boost::function<void(boost::system::error_code ec, size_t length)>
+ SocketCallbackFunction;
+
+ /// @brief Functor associated with the socket object.
+ ///
+ /// This functor calls a callback function specified in the constructor.
+ class SocketCallback {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// @param socket_callback Callback to be invoked by the functor upon
+ /// an event associated with the socket.
+ SocketCallback(SocketCallbackFunction socket_callback)
+ : callback_(socket_callback) {
+ }
+
+ /// @brief Operator called when event associated with a socket occurs.
+ ///
+ /// This operator returns immediately when received error code is
+ /// @c boost::system::error_code is equal to
+ /// @c boost::asio::error::operation_aborted, i.e. the callback is not
+ /// invoked.
+ ///
+ /// @param ec Error code.
+ /// @param length Data length.
+ void operator()(boost::system::error_code ec, size_t length = 0);
+
+ private:
+ /// @brief Supplied callback.
+ SocketCallbackFunction callback_;
+ };
+
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Reference to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ HttpConnection(asiolink::IOService& io_service,
+ HttpAcceptor& acceptor,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout);
+
+ /// @brief Destructor.
+ ///
+ /// Closes current connection.
+ ~HttpConnection();
+
+ /// @brief Asynchronously accepts new connection.
+ ///
+ /// When the connection is established successfully, the timeout timer is
+ /// setup and the asynchronous read from the socket is started.
+ void asyncAccept();
+
+ /// @brief Closes the socket.
+ void close();
+
+ /// @brief Starts asynchronous read from the socket.
+ ///
+ /// The data received over the socket are supplied to the HTTP parser until
+ /// the parser signals that the entire request has been received or until
+ /// the parser signals an error. In the former case the server creates an
+ /// HTTP response using supplied response creator object.
+ ///
+ /// In case of error the connection is stopped.
+ void doRead();
+
+private:
+
+ /// @brief Starts asynchronous write to the socket.
+ ///
+ /// The @c output_buf_ must contain the data to be sent.
+ ///
+ /// In case of error the connection is stopped.
+ void doWrite();
+
+ /// @brief Sends HTTP response asynchronously.
+ ///
+ /// Internally it calls @ref HttpConnection::doWrite to send the data.
+ ///
+ /// @param response Pointer to the HTTP response to be sent.
+ void asyncSendResponse(const ConstHttpResponsePtr& response);
+
+ /// @brief Local callback invoked when new connection is accepted.
+ ///
+ /// It invokes external (supplied via constructor) acceptor callback. If
+ /// the acceptor is not opened it returns immediately. If the connection
+ /// is accepted successfully the @ref HttpConnection::doRead is called.
+ ///
+ /// @param ec Error code.
+ void acceptorCallback(const boost::system::error_code& ec);
+
+ /// @brief Callback invoked when new data is received over the socket.
+ ///
+ /// This callback supplies the data to the HTTP parser and continues
+ /// parsing. When the parser signals end of the HTTP request the callback
+ /// prepares a response and starts asynchronous send over the socket.
+ ///
+ /// @param ec Error code.
+ /// @param length Length of the received data.
+ void socketReadCallback(boost::system::error_code ec,
+ size_t length);
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ void socketWriteCallback(boost::system::error_code ec,
+ size_t length);
+
+ /// @brief Callback invoked when the HTTP Request Timeout occurs.
+ ///
+ /// This callback creates HTTP response with Request Timeout error code
+ /// and sends it to the client.
+ void requestTimeoutCallback();
+
+ /// @brief Stops current connection.
+ void stopThisConnection();
+
+ /// @brief returns remote address in textual form
+ std::string getRemoteEndpointAddressAsText() const;
+
+ /// @brief Timer used to detect Request Timeout.
+ asiolink::IntervalTimer request_timer_;
+
+ /// @brief Configured Request Timeout in milliseconds.
+ long request_timeout_;
+
+ /// @brief Socket used by this connection.
+ asiolink::TCPSocket<SocketCallback> socket_;
+
+ /// @brief Reference to the TCP acceptor used to accept new connections.
+ HttpAcceptor& acceptor_;
+
+ /// @brief Connection pool holding this connection.
+ HttpConnectionPool& connection_pool_;
+
+ /// @brief Pointer to the @ref HttpResponseCreator object used to create
+ /// HTTP responses.
+ HttpResponseCreatorPtr response_creator_;
+
+ /// @brief Pointer to the request received over this connection.
+ HttpRequestPtr request_;
+
+ /// @brief Pointer to the HTTP request parser.
+ HttpRequestParserPtr parser_;
+
+ /// @brief External TCP acceptor callback.
+ HttpAcceptorCallback acceptor_callback_;
+
+ /// @brief Buffer for received data.
+ std::array<char, 4096> buf_;
+
+ /// @brief Buffer used for outbound data.
+ std::string output_buf_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/connection_pool.cc b/src/lib/http/connection_pool.cc
new file mode 100644
index 0000000000..8d39c02d4d
--- /dev/null
+++ b/src/lib/http/connection_pool.cc
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <asiolink/asio_wrapper.h>
+#include <http/connection_pool.h>
+
+namespace isc {
+namespace http {
+
+void
+HttpConnectionPool::start(const HttpConnectionPtr& connection) {
+ connections_.insert(connections_.end(), connection);
+ connection->asyncAccept();
+}
+
+void
+HttpConnectionPool::stop(const HttpConnectionPtr& connection) {
+ connections_.remove(connection);
+ connection->close();
+}
+
+void
+HttpConnectionPool::stopAll() {
+ for (auto connection = connections_.begin();
+ connection != connections_.end();
+ ++connection) {
+ (*connection)->close();
+ }
+ connections_.clear();
+}
+
+}
+}
diff --git a/src/lib/http/connection_pool.h b/src/lib/http/connection_pool.h
new file mode 100644
index 0000000000..b5c19b0f56
--- /dev/null
+++ b/src/lib/http/connection_pool.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_CONNECTION_POOL_H
+#define HTTP_CONNECTION_POOL_H
+
+#include <http/connection.h>
+#include <list>
+
+namespace isc {
+namespace http {
+
+/// @brief Pool of active HTTP connections.
+///
+/// The HTTP server is designed to handle many connections simultaneously.
+/// The communication between the client and the server may take long time
+/// and the server must be able to react on other events while the communication
+/// with the clients is in progress. Thus, the server must track active
+/// connections and gracefully close them when needed. An obvious case when the
+/// connections must be terminated by the server is when the shutdown signal
+/// is received.
+///
+/// This object is a simple container for the server connections which provides
+/// means to terminate them on request.
+class HttpConnectionPool {
+public:
+
+ /// @brief Start new connection.
+ ///
+ /// The connection is inserted to the pool and the
+ /// @ref HttpConnection::asyncAccept is invoked.
+ ///
+ /// @param connection Pointer to the new connection.
+ void start(const HttpConnectionPtr& connection);
+
+ /// @brief Stops a connection and removes it from the pool.
+ ///
+ /// If the connection is not found in the pool, this method is no-op.
+ ///
+ /// @param connection Pointer to the connection.
+ void stop(const HttpConnectionPtr& connection);
+
+ /// @brief Stops all connections and removes them from the pool.
+ void stopAll();
+
+protected:
+
+ /// @brief Set of connections.
+ std::list<HttpConnectionPtr> connections_;
+
+};
+
+}
+}
+
+#endif
+
diff --git a/src/lib/http/date_time.cc b/src/lib/http/date_time.cc
new file mode 100644
index 0000000000..a18116704f
--- /dev/null
+++ b/src/lib/http/date_time.cc
@@ -0,0 +1,156 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/date_time.h>
+#include <boost/date_time/time_facet.hpp>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <locale>
+#include <sstream>
+
+using namespace boost::local_time;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace http {
+
+HttpDateTime::HttpDateTime()
+ : time_(boost::posix_time::microsec_clock::universal_time()) {
+}
+
+HttpDateTime::HttpDateTime(const boost::posix_time::ptime& t)
+ : time_(t) {
+}
+
+std::string
+HttpDateTime::rfc1123Format() const {
+ return (toString("%a, %d %b %Y %H:%M:%S GMT", "RFC 1123"));
+}
+
+std::string
+HttpDateTime::rfc850Format() const {
+ return (toString("%A, %d-%b-%y %H:%M:%S GMT", "RFC 850"));
+}
+
+std::string
+HttpDateTime::asctimeFormat() const {
+ return (toString("%a %b %e %H:%M:%S %Y", "asctime"));
+}
+
+HttpDateTime
+HttpDateTime::fromRfc1123(const std::string& time_string) {
+ return (HttpDateTime(fromString(time_string,
+ "%a, %d %b %Y %H:%M:%S %ZP",
+ "RFC 1123")));
+}
+
+HttpDateTime
+HttpDateTime::fromRfc850(const std::string& time_string) {
+ return (HttpDateTime(fromString(time_string,
+ "%A, %d-%b-%y %H:%M:%S %ZP",
+ "RFC 850")));
+}
+
+HttpDateTime
+HttpDateTime::fromAsctime(const std::string& time_string) {
+ // The asctime() puts space instead of leading 0 in days of
+ // month. The %e # formatter of time_input_facet doesn't deal
+ // with this. To deal with this, we make a copy of the string
+ // holding formatted time and replace a space preceding day
+ // number with 0. Thanks to this workaround we can use the
+ // %d formatter which seems to work fine. This has a side
+ // effect of accepting timestamps such as Sun Nov 06 08:49:37 1994,
+ // but it should be ok to be liberal in this case.
+ std::string time_string_copy(time_string);
+ boost::replace_all(time_string_copy, " ", " 0");
+ return (HttpDateTime(fromString(time_string_copy,
+ "%a %b %d %H:%M:%S %Y",
+ "asctime",
+ false)));
+}
+
+HttpDateTime
+HttpDateTime::fromAny(const std::string& time_string) {
+ HttpDateTime date_time;
+ // Try to parse as a timestamp specified in RFC 1123 format.
+ try {
+ date_time = fromRfc1123(time_string);
+ return (date_time);
+ } catch (...) {
+ // Ignore errors, simply try different format.
+ }
+
+ // Try to parse as a timestamp specified in RFC 850 format.
+ try {
+ date_time = fromRfc850(time_string);
+ return (date_time);
+ } catch (...) {
+ // Ignore errors, simply try different format.
+ }
+
+ // Try to parse as a timestamp output by asctime() function.
+ try {
+ date_time = fromAsctime(time_string);
+ } catch (...) {
+ isc_throw(HttpTimeConversionError,
+ "unsupported time format of the '" << time_string
+ << "'");
+ }
+
+ return (date_time);
+
+}
+
+std::string
+HttpDateTime::toString(const std::string& format,
+ const std::string& method_name) const {
+ std::ostringstream s;
+ // Create raw pointer. The output stream will take responsibility for
+ // deleting the object.
+ time_facet* df(new time_facet(format.c_str()));
+ s.imbue(std::locale(std::locale::classic(), df));
+
+ // Convert time value to a string.
+ s << time_;
+ if (s.fail()) {
+ isc_throw(HttpTimeConversionError, "unable to convert "
+ << "time value of '" << time_ << "'"
+ << " to " << method_name << " format");
+ }
+ return (s.str());
+}
+
+
+ptime
+HttpDateTime::fromString(const std::string& time_string,
+ const std::string& format,
+ const std::string& method_name,
+ const bool zone_check) {
+ std::istringstream s(time_string);
+ // Create raw pointer. The input stream will take responsibility for
+ // deleting the object.
+ time_input_facet* tif(new time_input_facet(format));
+ s.imbue(std::locale(std::locale::classic(), tif));
+
+ time_zone_ptr zone(new posix_time_zone("GMT"));
+ local_date_time ldt = local_microsec_clock::local_time(zone);
+
+ // Parse the time value. The stream will not automatically detect whether
+ // the zone is GMT. We need to check it on our own.
+ s >> ldt;
+ if (s.fail() ||
+ (zone_check && (!ldt.zone() ||
+ ldt.zone()->std_zone_abbrev() != "GMT"))) {
+ isc_throw(HttpTimeConversionError, "unable to parse "
+ << method_name << " time value of '"
+ << time_string << "'");
+ }
+
+ return (ldt.local_time());
+}
+
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/date_time.h b/src/lib/http/date_time.h
new file mode 100644
index 0000000000..16875d476c
--- /dev/null
+++ b/src/lib/http/date_time.h
@@ -0,0 +1,160 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_DATE_TIME_H
+#define HTTP_DATE_TIME_H
+
+#include <exceptions/exceptions.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when there is an error during time conversion.
+///
+/// The most common reason for this exception is that the unsupported time
+/// format was used as an input to the time parsing functions.
+class HttpTimeConversionError : public Exception {
+public:
+ HttpTimeConversionError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief This class parses and generates time values used in HTTP.
+///
+/// The HTTP protocol have historically allowed 3 different date/time formats
+/// (see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html). These are:
+/// - Sun, 06 Nov 1994 08:49:37 GMT
+/// - Sunday, 06-Nov-94 08:49:37 GMT
+/// - Sun Nov 6 08:49:37 1994
+///
+/// The first format is preferred but implementations must also support
+/// remaining two obsolete formats for compatibility. This class implements
+/// parsers and generators for all three formats. It uses @c boost::posix_time
+/// to represent time and date. It uses @c boost::date_time::time_facet
+/// and @c boost::date_time::time_input_facet to generate and parse the
+/// timestamps.
+class HttpDateTime {
+public:
+
+ /// @brief Default constructor.
+ ///
+ /// Sets current universal time as time value.
+ HttpDateTime();
+
+ /// @brief Construct from @c boost::posix_time::ptime object.
+ ///
+ /// @param t time value to be set.
+ explicit HttpDateTime(const boost::posix_time::ptime& t);
+
+ /// @brief Returns time encapsulated by this class.
+ ///
+ /// @return @c boost::posix_time::ptime value encapsulated by the instance
+ /// of this class.
+ boost::posix_time::ptime getPtime() const {
+ return (time_);
+ }
+
+ /// @brief Returns time value formatted as specified in RFC 1123.
+ ///
+ /// @return A string containing time value formatted as
+ /// Sun, 06 Nov 1994 08:49:37 GMT.
+ std::string rfc1123Format() const;
+
+ /// @brief Returns time value formatted as specified in RFC 850.
+ ///
+ /// @return A string containing time value formatted as
+ /// Sunday, 06-Nov-94 08:49:37 GMT.
+ std::string rfc850Format() const;
+
+ /// @brief Returns time value formatted as output of ANSI C's
+ /// asctime().
+ ///
+ /// @return A string containing time value formatted as
+ /// Sun Nov 6 08:49:37 1994.
+ std::string asctimeFormat() const;
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted as specified in RFC 1123.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided timestamp has invalid
+ /// format.
+ static HttpDateTime fromRfc1123(const std::string& time_string);
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted as specified in RFC 850.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided timestamp has invalid
+ /// format.
+ static HttpDateTime fromRfc850(const std::string& time_string);
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted as output from asctime() function.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided timestamp has invalid
+ /// format.
+ static HttpDateTime fromAsctime(const std::string& time_string);
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted in one of the supported formats.
+ ///
+ /// This method will detect the format of the time value and parse it.
+ /// It tries parsing the value in the following order:
+ /// - a format specified in RFC 1123,
+ /// - a format specified in RFC 850,
+ /// - a format of asctime output.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided value doesn't match any
+ /// of the supported formats.
+ static HttpDateTime fromAny(const std::string& time_string);
+
+private:
+
+ /// @brief Generic method formatting a time value to a specified format.
+ ////
+ /// @param format Time format as accepted by the
+ /// @c boost::date_time::time_facet.
+ std::string toString(const std::string& format,
+ const std::string& method_name) const;
+
+ /// @brief Generic method parsing time value and converting it to the
+ /// instance of @c boost::posix_time::ptime.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @param format Time format as accepted by the
+ /// @c boost::date_time::time_input_facet.
+ /// @param method_name Name of the expected format to appear in the error
+ /// message if parsing fails, e.g. RFC 1123, RFC 850 or asctime.
+ /// @param zone_check Indicates if the time zone name should be validated
+ /// during parsing. This should be set to false for the formats which
+ /// lack time zones (e.g. asctime).
+ ///
+ /// @return Instance of the @ref boost::posix_time::ptime created from the
+ /// input string.
+ /// @throw HttpTimeConversionError if provided value doesn't match the
+ /// specified format.
+ static boost::posix_time::ptime
+ fromString(const std::string& time_string, const std::string& format,
+ const std::string& method_name, const bool zone_check = true);
+
+ /// @brief Time value encapsulated by this class instance.
+ boost::posix_time::ptime time_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/header_context.h b/src/lib/http/header_context.h
new file mode 100644
index 0000000000..125591186e
--- /dev/null
+++ b/src/lib/http/header_context.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_HEADER_CONTEXT_H
+#define HTTP_HEADER_CONTEXT_H
+
+#include <string>
+
+namespace isc {
+namespace http {
+
+struct HttpHeaderContext {
+ std::string name_;
+ std::string value_;
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/http_acceptor.h b/src/lib/http/http_acceptor.h
new file mode 100644
index 0000000000..4d52f7b181
--- /dev/null
+++ b/src/lib/http/http_acceptor.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_ACCEPTOR_H
+#define HTTP_ACCEPTOR_H
+
+#include <asiolink/tcp_acceptor.h>
+#include <boost/function.hpp>
+#include <boost/system/system_error.hpp>
+
+namespace isc {
+namespace http {
+
+/// @brief Type of the callback for the TCP acceptor used in this library.
+typedef boost::function<void(const boost::system::error_code&)>
+HttpAcceptorCallback;
+
+/// @brief Type of the TCP acceptor used in this library.
+typedef asiolink::TCPAcceptor<HttpAcceptorCallback> HttpAcceptor;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/http_log.cc b/src/lib/http/http_log.cc
new file mode 100644
index 0000000000..64892f26f8
--- /dev/null
+++ b/src/lib/http/http_log.cc
@@ -0,0 +1,19 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// Defines the logger used by the top-level component of kea-dhcp-ddns.
+
+#include <http/http_log.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Defines the logger used within libkea-http library.
+isc::log::Logger http_logger("http");
+
+} // namespace http
+} // namespace isc
+
diff --git a/src/lib/http/http_log.h b/src/lib/http/http_log.h
new file mode 100644
index 0000000000..498c6162b9
--- /dev/null
+++ b/src/lib/http/http_log.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_LOG_H
+#define HTTP_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <http/http_messages.h>
+
+namespace isc {
+namespace http {
+
+/// Define the loggers used within libkea-http library.
+extern isc::log::Logger http_logger;
+
+} // namespace http
+} // namespace isc
+
+#endif // HTTP_LOG_H
diff --git a/src/lib/http/http_messages.mes b/src/lib/http/http_messages.mes
new file mode 100644
index 0000000000..ad317fbf0b
--- /dev/null
+++ b/src/lib/http/http_messages.mes
@@ -0,0 +1,45 @@
+# Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+$NAMESPACE isc::http
+
+% HTTP_CONNECTION_STOP stopping HTTP connection from %1
+This debug message is issued when one of the HTTP connections is stopped.
+The connection can be stopped as a result of an error or after the
+successful message exchange with a client.
+
+% HTTP_CONNECTION_STOP_FAILED stopping HTTP connection failed
+This error message is issued when an error occurred during closing a
+HTTP connection with a client.
+
+% HTTP_DATA_RECEIVED received %1 bytes from %2
+This debug message is issued when the server receives a chunk of data from
+the remote endpoint. This may include the whole request or only a part
+of the request. The first argument specifies the amount of received data.
+The second argument specifies an address of the remote endpoint which
+produced the data.
+
+% HTTP_REQUEST_RECEIVED received HTTP request from %1
+This debug message is issued when the server finished receiving a HTTP
+request from the remote endpoint. The address of the remote endpoint is
+specified as an argument.
+
+% HTTP_REQUEST_RECEIVE_START start receiving request from %1 with timeout %2
+This debug message is issued when the server starts receiving new request
+over the established connection. The first argument specifies the address
+of the remote endpoint. The second argument specifies request timeout in
+seconds.
+
+% HTTP_REQUEST_TIMEOUT_OCCURRED HTTP request timeout occurred when communicating with %1
+This debug message is issued when the HTTP request timeout has occurred and
+the server is going to send a response with Http Request timeout status
+code.
+
+% HTTP_RESPONSE_SEND sending HTTP response %1 to %2
+This debug message is issued when the server is starting to send a HTTP
+response to a remote endpoint. The first argument holds basic information
+about the response (HTTP version number and status code). The second
+argument specifies an address of the remote endpoint.
diff --git a/src/lib/http/http_types.h b/src/lib/http/http_types.h
new file mode 100644
index 0000000000..285096643c
--- /dev/null
+++ b/src/lib/http/http_types.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_TYPES_H
+#define HTTP_TYPES_H
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP protocol version.
+struct HttpVersion {
+ unsigned major_; ///< Major HTTP version.
+ unsigned minor_; ///< Minor HTTP version.
+
+ /// @brief Constructor.
+ ///
+ /// @param major Major HTTP version.
+ /// @param minor Minor HTTP version.
+ explicit HttpVersion(const unsigned major, const unsigned minor)
+ : major_(major), minor_(minor) {
+ }
+
+ /// @brief Operator less.
+ ///
+ /// @param rhs Version to compare to.
+ bool operator<(const HttpVersion& rhs) const {
+ return ((major_ < rhs.major_) ||
+ ((major_ == rhs.major_) && (minor_ < rhs.minor_)));
+ }
+
+ /// @brief Operator equal.
+ ///
+ /// @param rhs Version to compare to.
+ bool operator==(const HttpVersion& rhs) const {
+ return ((major_ == rhs.major_) && (minor_ == rhs.minor_));
+ }
+
+ /// @brief Operator not equal.
+ ///
+ /// @param rhs Version to compare to.
+ bool operator!=(const HttpVersion& rhs) const {
+ return (!operator==(rhs));
+ }
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/listener.cc b/src/lib/http/listener.cc
new file mode 100644
index 0000000000..b0e9d2c061
--- /dev/null
+++ b/src/lib/http/listener.cc
@@ -0,0 +1,219 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/tcp_endpoint.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/http_acceptor.h>
+#include <http/listener.h>
+#include <boost/scoped_ptr.hpp>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace http {
+
+/// @brief Implementation of the @ref HttpListener.
+class HttpListenerImpl {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new server endpoint using the specified IP
+ /// address and port. It also validates other specified parameters.
+ ///
+ /// This constructor does not start accepting new connections! To start
+ /// accepting connections run @ref HttpListener::start.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the HTTP service should run.
+ /// @param server_port Port number on which the HTTP service should run.
+ /// @param creator_factory Pointer to the caller-defined
+ /// @ref HttpResponseCreatorFactory derivation which should be used to
+ /// create @ref HttpResponseCreator instances.
+ /// @param request_timeout Timeout after which the HTTP Request Timeout
+ /// is generated.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListenerImpl(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout);
+
+ /// @brief Returns reference to the current listener endpoint.
+ const TCPEndpoint& getEndpoint() const;
+
+ /// @brief Starts accepting new connections.
+ ///
+ /// This method starts accepting and handling new HTTP connections on
+ /// the IP address and port number specified in the constructor.
+ ///
+ /// If the method is invoked successfully, it must not be invoked again
+ /// until @ref HttpListener::stop is called.
+ ///
+ /// @throw HttpListenerError if an error occurred.
+ void start();
+
+ /// @brief Stops all active connections and shuts down the service.
+ void stop();
+
+private:
+
+ /// @brief Creates @ref HttpConnection instance and adds it to the
+ /// pool of active connections.
+ ///
+ /// The next accepted connection will be handled by this instance.
+ void accept();
+
+ /// @brief Callback invoked when the new connection is accepted.
+ ///
+ /// It calls @ref HttpListener::accept to create new @ref HttpConnection
+ /// instance.
+ ///
+ /// @param ec Error code passed to the handler. This is currently ignored.
+ void acceptHandler(const boost::system::error_code& ec);
+
+ /// @brief Reference to the IO service.
+ asiolink::IOService& io_service_;
+
+ /// @brief Acceptor instance.
+ HttpAcceptor acceptor_;
+
+ /// @brief Pointer to the endpoint representing IP address and port on
+ /// which the service is running.
+ boost::scoped_ptr<asiolink::TCPEndpoint> endpoint_;
+
+ /// @brief Pool of active connections.
+ HttpConnectionPool connections_;
+
+ /// @brief Pointer to the @ref HttpResponseCreatorFactory.
+ HttpResponseCreatorFactoryPtr creator_factory_;
+
+ /// @brief Timeout for HTTP Request Timeout desired.
+ long request_timeout_;
+};
+
+HttpListenerImpl::HttpListenerImpl(IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout)
+ : io_service_(io_service), acceptor_(io_service),
+ endpoint_(), creator_factory_(creator_factory),
+ request_timeout_(request_timeout) {
+ // Try creating an endpoint. This may cause exceptions.
+ try {
+ endpoint_.reset(new TCPEndpoint(server_address, server_port));
+
+ } catch (...) {
+ isc_throw(HttpListenerError, "unable to create TCP endpoint for "
+ << server_address << ":" << server_port);
+ }
+
+ // The factory must not be null.
+ if (!creator_factory_) {
+ isc_throw(HttpListenerError, "HttpResponseCreatorFactory must not"
+ " be null");
+ }
+
+ // Request timeout is signed and must be greater than 0.
+ if (request_timeout_ <= 0) {
+ isc_throw(HttpListenerError, "Invalid desired HTTP request timeout "
+ << request_timeout_);
+ }
+}
+
+const TCPEndpoint&
+HttpListenerImpl::getEndpoint() const {
+ return (*endpoint_);
+}
+
+void
+HttpListenerImpl::start() {
+ try {
+ acceptor_.open(*endpoint_);
+ acceptor_.setOption(HttpAcceptor::ReuseAddress(true));
+ acceptor_.bind(*endpoint_);
+ acceptor_.listen();
+
+ } catch (const boost::system::system_error& ex) {
+ stop();
+ isc_throw(HttpListenerError, "unable to setup TCP acceptor for "
+ "listening to the incoming HTTP requests: " << ex.what());
+ }
+
+ accept();
+}
+
+void
+HttpListenerImpl::stop() {
+ connections_.stopAll();
+ acceptor_.close();
+}
+
+void
+HttpListenerImpl::accept() {
+ // In some cases we may need HttpResponseCreator instance per connection.
+ // But, the factory may also return the same instance each time. It
+ // depends on the use case.
+ HttpResponseCreatorPtr response_creator = creator_factory_->create();
+ HttpAcceptorCallback acceptor_callback =
+ boost::bind(&HttpListenerImpl::acceptHandler, this, _1);
+ HttpConnectionPtr conn(new HttpConnection(io_service_, acceptor_,
+ connections_,
+ response_creator,
+ acceptor_callback,
+ request_timeout_));
+ // Add this new connection to the pool.
+ connections_.start(conn);
+}
+
+void
+HttpListenerImpl::acceptHandler(const boost::system::error_code&) {
+ // The new connection has arrived. Set the acceptor to continue
+ // accepting new connections.
+ accept();
+}
+
+HttpListener::HttpListener(IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout)
+ : impl_(new HttpListenerImpl(io_service, server_address, server_port,
+ creator_factory, request_timeout)) {
+}
+
+HttpListener::~HttpListener() {
+ stop();
+}
+
+IOAddress
+HttpListener::getLocalAddress() const {
+ return (impl_->getEndpoint().getAddress());
+}
+
+uint16_t
+HttpListener::getLocalPort() const {
+ return (impl_->getEndpoint().getPort());
+}
+
+void
+HttpListener::start() {
+ impl_->start();
+}
+
+void
+HttpListener::stop() {
+ impl_->stop();
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/listener.h b/src/lib/http/listener.h
new file mode 100644
index 0000000000..1659d8868b
--- /dev/null
+++ b/src/lib/http/listener.h
@@ -0,0 +1,120 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_LISTENER_H
+#define HTTP_LISTENER_H
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <http/response_creator_factory.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace http {
+
+/// @brief A generic error raised by the @ref HttpListener class.
+class HttpListenerError : public Exception {
+public:
+ HttpListenerError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief HttpListener implementation.
+class HttpListenerImpl;
+
+/// @brief HTTP listener.
+///
+/// This class is an entry point to the use of HTTP services in Kea.
+/// It creates a TCP acceptor service on the specified address and
+/// port and listens to the incoming HTTP connections. The constructor
+/// receives a pointer to the implementation of the
+/// @ref HttpResponseCreatorFactory, which is used by the @ref HttpListener
+/// to create/retrieve an instance of the @ref HttpResponseCreator when the
+/// new HTTP response needs to be generated. The @ref HttpResponseCreator
+/// creates an object derived from the @ref HttpResponse class, encapsulating
+/// a HTTP response following some specific rules, e.g. having
+/// "application/json" content type.
+///
+/// When the listener is started it creates an instance of a @ref HttpConnection
+/// and stores them in the pool of active connections. The @ref HttpConnection
+/// is responsible for managing the next connection received and receiving the
+/// HTTP request and sending appropriate response. The listener can handle
+/// many HTTP connections simultaneously.
+///
+/// When the @ref HttpListener::stop is invoked, all active connections are
+/// closed and the listener stops accepting new connections.
+class HttpListener {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new server endpoint using the specified IP
+ /// address and port. It also validates other specified parameters.
+ ///
+ /// This constructor does not start accepting new connections! To start
+ /// accepting connections run @ref HttpListener::start.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the HTTP service should run.
+ /// @param server_port Port number on which the HTTP service should run.
+ /// @param creator_factory Pointer to the caller-defined
+ /// @ref HttpResponseCreatorFactory derivation which should be used to
+ /// create @ref HttpResponseCreator instances.
+ /// @param request_timeout Timeout after which the HTTP Request Timeout
+ /// is generated.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListener(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout);
+
+ /// @brief Destructor.
+ ///
+ /// Stops all active connections and closes TCP acceptor service.
+ ~HttpListener();
+
+ /// @brief Returns local address on which server is listening.
+ asiolink::IOAddress getLocalAddress() const;
+
+ /// @brief Returns local port on which server is listening.
+ uint16_t getLocalPort() const;
+
+ /// @brief Starts accepting new connections.
+ ///
+ /// This method starts accepting and handling new HTTP connections on
+ /// the IP address and port number specified in the constructor.
+ ///
+ /// If the method is invoked successfully, it must not be invoked again
+ /// until @ref HttpListener::stop is called.
+ ///
+ /// @throw HttpListenerError if an error occurred.
+ void start();
+
+ /// @brief Stops all active connections and shuts down the service.
+ void stop();
+
+private:
+
+ /// @brief Pointer to the implementation of the @ref HttpListener.
+ boost::shared_ptr<HttpListenerImpl> impl_;
+
+};
+
+/// @brief Pointer to the @ref HttpListener.
+typedef boost::shared_ptr<HttpListener> HttpListenerPtr;
+
+/// @brief Pointer to the const @ref HttpListener.
+typedef boost::shared_ptr<const HttpListener> ConstHttpListenerPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/post_request.cc b/src/lib/http/post_request.cc
new file mode 100644
index 0000000000..94caca1b26
--- /dev/null
+++ b/src/lib/http/post_request.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/post_request.h>
+
+namespace isc {
+namespace http {
+
+PostHttpRequest::PostHttpRequest()
+ : HttpRequest() {
+ requireHttpMethod(Method::HTTP_POST);
+ requireHeader("Content-Length");
+ requireHeader("Content-Type");
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/post_request.h b/src/lib/http/post_request.h
new file mode 100644
index 0000000000..dd16143734
--- /dev/null
+++ b/src/lib/http/post_request.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_POST_REQUEST_H
+#define HTTP_POST_REQUEST_H
+
+#include <http/request.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+class PostHttpRequest;
+
+/// @brief Pointer to @ref PostHttpRequest.
+typedef boost::shared_ptr<PostHttpRequest> PostHttpRequestPtr;
+/// @brief Pointer to const @ref PostHttpRequest.
+typedef boost::shared_ptr<const PostHttpRequest> ConstPostHttpRequestPtr;
+
+/// @brief Represents HTTP POST request.
+///
+/// Instructs the parent class to require:
+/// - HTTP POST message type,
+/// - Content-Length header,
+/// - Content-Type header.
+class PostHttpRequest : public HttpRequest {
+public:
+
+ /// @brief Constructor.
+ PostHttpRequest();
+};
+
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/post_request_json.cc b/src/lib/http/post_request_json.cc
new file mode 100644
index 0000000000..c871dbbca0
--- /dev/null
+++ b/src/lib/http/post_request_json.cc
@@ -0,0 +1,75 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/post_request_json.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace http {
+
+PostHttpRequestJson::PostHttpRequestJson()
+ : PostHttpRequest(), json_() {
+ requireHeaderValue("Content-Type", "application/json");
+}
+
+void
+PostHttpRequestJson::finalize() {
+ if (!created_) {
+ create();
+ }
+
+ // Parse JSON body and store.
+ parseBodyAsJson();
+ finalized_ = true;
+}
+
+void
+PostHttpRequestJson::reset() {
+ PostHttpRequest::reset();
+ json_.reset();
+}
+
+ConstElementPtr
+PostHttpRequestJson::getBodyAsJson() const {
+ checkFinalized();
+ return (json_);
+}
+
+ConstElementPtr
+PostHttpRequestJson::getJsonElement(const std::string& element_name) const {
+ try {
+ ConstElementPtr body = getBodyAsJson();
+ if (body) {
+ const std::map<std::string, ConstElementPtr>& map_value = body->mapValue();
+ auto map_element = map_value.find(element_name);
+ if (map_element != map_value.end()) {
+ return (map_element->second);
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ isc_throw(HttpRequestJsonError, "unable to get JSON element "
+ << element_name << ": " << ex.what());
+ }
+ return (ConstElementPtr());
+}
+
+void
+PostHttpRequestJson::parseBodyAsJson() {
+ try {
+ // Only parse the body if it hasn't been parsed yet.
+ if (!json_ && !context_->body_.empty()) {
+ json_ = Element::fromJSON(context_->body_);
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(HttpRequestJsonError, "unable to parse the body of the HTTP"
+ " request: " << ex.what());
+ }
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/post_request_json.h b/src/lib/http/post_request_json.h
new file mode 100644
index 0000000000..a51b484cf1
--- /dev/null
+++ b/src/lib/http/post_request_json.h
@@ -0,0 +1,83 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_POST_REQUEST_JSON_H
+#define HTTP_POST_REQUEST_JSON_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <http/post_request.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when body of the HTTP message is not JSON.
+class HttpRequestJsonError : public HttpRequestError {
+public:
+ HttpRequestJsonError(const char* file, size_t line, const char* what) :
+ HttpRequestError(file, line, what) { };
+};
+
+class PostHttpRequestJson;
+
+/// @brief Pointer to @ref PostHttpRequestJson.
+typedef boost::shared_ptr<PostHttpRequestJson> PostHttpRequestJsonPtr;
+/// @brief Pointer to const @ref PostHttpRequestJson.
+typedef boost::shared_ptr<const PostHttpRequestJson> ConstPostHttpRequestJsonPtr;
+
+/// @brief Represents HTTP POST request with JSON body.
+///
+/// In addition to the requirements specified by the @ref PostHttpRequest
+/// this class requires that the "Content-Type" is "application/json".
+///
+/// This class provides methods to parse and retrieve JSON data structures.
+class PostHttpRequestJson : public PostHttpRequest {
+public:
+
+ /// @brief Constructor.
+ PostHttpRequestJson();
+
+ /// @brief Complete parsing of the HTTP request.
+ ///
+ /// This method parses the JSON body into the structure of
+ /// @ref data::ConstElementPtr objects.
+ virtual void finalize();
+
+ /// @brief Reset the state of the object.
+ virtual void reset();
+
+ /// @brief Retrieves JSON body.
+ ///
+ /// @return Pointer to the root element of the JSON structure.
+ /// @throw HttpRequestJsonError if an error occurred.
+ data::ConstElementPtr getBodyAsJson() const;
+
+ /// @brief Retrieves a single JSON element.
+ ///
+ /// The element must be at top level of the JSON structure.
+ ///
+ /// @param element_name Element name.
+ ///
+ /// @return Pointer to the specified element or NULL if such element
+ /// doesn't exist.
+ /// @throw HttpRequestJsonError if an error occurred.
+ data::ConstElementPtr getJsonElement(const std::string& element_name) const;
+
+protected:
+
+ void parseBodyAsJson();
+
+ /// @brief Pointer to the parsed JSON body.
+ data::ConstElementPtr json_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/request.cc b/src/lib/http/request.cc
new file mode 100644
index 0000000000..206ab332fe
--- /dev/null
+++ b/src/lib/http/request.cc
@@ -0,0 +1,259 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/request.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace http {
+
+HttpRequest::HttpRequest()
+ : required_methods_(),required_versions_(), required_headers_(),
+ created_(false), finalized_(false), method_(Method::HTTP_METHOD_UNKNOWN),
+ headers_(), context_(new HttpRequestContext()) {
+}
+
+HttpRequest::~HttpRequest() {
+}
+
+void
+HttpRequest::requireHttpMethod(const HttpRequest::Method& method) {
+ required_methods_.insert(method);
+}
+
+void
+HttpRequest::requireHttpVersion(const HttpVersion& version) {
+ required_versions_.insert(version);
+}
+
+void
+HttpRequest::requireHeader(const std::string& header_name) {
+ // Empty value denotes that the header is required but no specific
+ // value is expected.
+ required_headers_[header_name] = "";
+}
+
+void
+HttpRequest::requireHeaderValue(const std::string& header_name,
+ const std::string& header_value) {
+ required_headers_[header_name] = header_value;
+}
+
+bool
+HttpRequest::requiresBody() const {
+ // If Content-Length is required the body must exist too. There may
+ // be probably some cases when Content-Length is not provided but
+ // the body is provided. But, probably not in our use cases.
+ return (required_headers_.find("Content-Length") != required_headers_.end());
+}
+
+void
+HttpRequest::create() {
+ try {
+ // The RequestParser doesn't validate the method name. Thus, this
+ // may throw an exception. But, we're fine with lower case names,
+ // e.g. get, post etc.
+ method_ = methodFromString(context_->method_);
+
+ // Check if the method is allowed for this request.
+ if (!inRequiredSet(method_, required_methods_)) {
+ isc_throw(BadValue, "use of HTTP " << methodToString(method_)
+ << " not allowed");
+ }
+
+ // Check if the HTTP version is allowed for this request.
+ if (!inRequiredSet(HttpVersion(context_->http_version_major_,
+ context_->http_version_minor_),
+ required_versions_)) {
+ isc_throw(BadValue, "use of HTTP version "
+ << context_->http_version_major_ << "."
+ << context_->http_version_minor_
+ << " not allowed");
+ }
+
+ // Copy headers from the context.
+ for (auto header = context_->headers_.begin();
+ header != context_->headers_.end();
+ ++header) {
+ headers_[header->name_] = header->value_;
+ }
+
+ // Iterate over required headers and check that they exist
+ // in the HTTP request.
+ for (auto req_header = required_headers_.begin();
+ req_header != required_headers_.end();
+ ++req_header) {
+ auto header = headers_.find(req_header->first);
+ if (header == headers_.end()) {
+ isc_throw(BadValue, "required header " << req_header->first
+ << " not found in the HTTP request");
+ } else if (!req_header->second.empty() &&
+ header->second != req_header->second) {
+ // If specific value is required for the header, check
+ // that the value in the HTTP request matches it.
+ isc_throw(BadValue, "required header's " << header->first
+ << " value is " << req_header->second
+ << ", but " << header->second << " was found");
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ // Reset the state of the object if we failed at any point.
+ reset();
+ isc_throw(HttpRequestError, ex.what());
+ }
+
+ // All ok.
+ created_ = true;
+}
+
+void
+HttpRequest::finalize() {
+ if (!created_) {
+ create();
+ }
+
+ // In this specific case, we don't need to do anything because the
+ // body is retrieved from the context object directly. We also don't
+ // know what type of body we have received. Derived classes should
+ // override this method and handle various types of bodies.
+ finalized_ = true;
+}
+
+void
+HttpRequest::reset() {
+ created_ = false;
+ finalized_ = false;
+ method_ = HttpRequest::Method::HTTP_METHOD_UNKNOWN;
+ headers_.clear();
+}
+
+HttpRequest::Method
+HttpRequest::getMethod() const {
+ checkCreated();
+ return (method_);
+}
+
+std::string
+HttpRequest::getUri() const {
+ checkCreated();
+ return (context_->uri_);
+}
+
+HttpVersion
+HttpRequest::getHttpVersion() const {
+ checkCreated();
+ return (HttpVersion(context_->http_version_major_,
+ context_->http_version_minor_));
+}
+
+std::string
+HttpRequest::getHeaderValue(const std::string& header) const {
+ checkCreated();
+
+ auto header_it = headers_.find(header);
+ if (header_it != headers_.end()) {
+ return (header_it->second);
+ }
+ // No such header.
+ isc_throw(HttpRequestNonExistingHeader, header << " HTTP header"
+ " not found in the request");
+}
+
+uint64_t
+HttpRequest::getHeaderValueAsUint64(const std::string& header) const {
+ // This will throw an exception if the header doesn't exist.
+ std::string header_value = getHeaderValue(header);
+
+ try {
+ return (boost::lexical_cast<uint64_t>(header_value));
+
+ } catch (const boost::bad_lexical_cast& ex) {
+ // The specified header does exist, but the value is not a number.
+ isc_throw(HttpRequestError, header << " HTTP header value "
+ << header_value << " is not a valid number");
+ }
+}
+
+std::string
+HttpRequest::getBody() const {
+ checkFinalized();
+ return (context_->body_);
+}
+
+void
+HttpRequest::checkCreated() const {
+ if (!created_) {
+ isc_throw(HttpRequestError, "unable to retrieve values of HTTP"
+ " request because the HttpRequest::create() must be"
+ " called first. This is a programmatic error");
+ }
+}
+
+void
+HttpRequest::checkFinalized() const {
+ if (!finalized_) {
+ isc_throw(HttpRequestError, "unable to retrieve body of HTTP"
+ " request because the HttpRequest::finalize() must be"
+ " called first. This is a programmatic error");
+ }
+}
+
+template<typename T>
+bool
+HttpRequest::inRequiredSet(const T& element,
+ const std::set<T>& element_set) const {
+ return (element_set.empty() || element_set.count(element) > 0);
+}
+
+
+HttpRequest::Method
+HttpRequest::methodFromString(std::string method) const {
+ boost::to_upper(method);
+ if (method == "GET") {
+ return (Method::HTTP_GET);
+ } else if (method == "POST") {
+ return (Method::HTTP_POST);
+ } else if (method == "HEAD") {
+ return (Method::HTTP_HEAD);
+ } else if (method == "PUT") {
+ return (Method::HTTP_PUT);
+ } else if (method == "DELETE") {
+ return (Method::HTTP_DELETE);
+ } else if (method == "OPTIONS") {
+ return (Method::HTTP_OPTIONS);
+ } else if (method == "CONNECT") {
+ return (Method::HTTP_CONNECT);
+ } else {
+ isc_throw(HttpRequestError, "unknown HTTP method " << method);
+ }
+}
+
+std::string
+HttpRequest::methodToString(const HttpRequest::Method& method) const {
+ switch (method) {
+ case Method::HTTP_GET:
+ return ("GET");
+ case Method::HTTP_POST:
+ return ("POST");
+ case Method::HTTP_HEAD:
+ return ("HEAD");
+ case Method::HTTP_PUT:
+ return ("PUT");
+ case Method::HTTP_DELETE:
+ return ("DELETE");
+ case Method::HTTP_OPTIONS:
+ return ("OPTIONS");
+ case Method::HTTP_CONNECT:
+ return ("CONNECT");
+ default:
+ return ("unknown HTTP method");
+ }
+}
+
+}
+}
diff --git a/src/lib/http/request.h b/src/lib/http/request.h
new file mode 100644
index 0000000000..07cd29c7e3
--- /dev/null
+++ b/src/lib/http/request.h
@@ -0,0 +1,296 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_REQUEST_H
+#define HTTP_REQUEST_H
+
+#include <exceptions/exceptions.h>
+#include <http/http_types.h>
+#include <http/request_context.h>
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <set>
+#include <stdint.h>
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpRequest class.
+class HttpRequestError : public Exception {
+public:
+ HttpRequestError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when attempt is made to retrieve a
+/// non-existing header.
+class HttpRequestNonExistingHeader : public HttpRequestError {
+public:
+ HttpRequestNonExistingHeader(const char* file, size_t line,
+ const char* what) :
+ HttpRequestError(file, line, what) { };
+};
+
+class HttpRequest;
+
+/// @brief Pointer to the @ref HttpRequest object.
+typedef boost::shared_ptr<HttpRequest> HttpRequestPtr;
+
+/// @brief Pointer to the const @ref HttpRequest object.
+typedef boost::shared_ptr<const HttpRequest> ConstHttpRequestPtr;
+
+/// @brief Represents HTTP request message.
+///
+/// This object represents parsed HTTP message. The @ref HttpRequestContext
+/// contains raw data used as input for this object. This class interprets the
+/// data. In particular, it verifies that the appropriate method, HTTP version,
+/// and headers were used. The derivations of this class provide specializations
+/// and specify the HTTP methods, versions and headers supported/required in
+/// the specific use cases.
+///
+/// For example, the @ref PostHttpRequest class derives from @ref HttpRequest
+/// and it requires that parsed messages use POST method. The
+/// @ref PostHttpRequestJson, which derives from @ref PostHttpRequest requires
+/// that the POST message includes body holding a JSON structure and provides
+/// methods to parse the JSON body.
+class HttpRequest {
+public:
+
+ /// @brief HTTP methods.
+ enum class Method {
+ HTTP_GET,
+ HTTP_POST,
+ HTTP_HEAD,
+ HTTP_PUT,
+ HTTP_DELETE,
+ HTTP_OPTIONS,
+ HTTP_CONNECT,
+ HTTP_METHOD_UNKNOWN
+ };
+
+ /// @brief Constructor.
+ ///
+ /// Creates new context (@ref HttpRequestContext).
+ HttpRequest();
+
+ /// @brief Destructor.
+ virtual ~HttpRequest();
+
+ /// @brief Returns reference to the @ref HttpRequestContext.
+ ///
+ /// This method is called by the @ref HttpRequestParser to retrieve the
+ /// context in which parsed data is stored.
+ ///
+ /// @return Pointer to the underlying @ref HttpRequestContext.
+ const HttpRequestContextPtr& context() const {
+ return (context_);
+ }
+
+ /// @brief Specifies an HTTP method allowed for the request.
+ ///
+ /// Allowed methods must be specified prior to calling @ref create method.
+ /// If no method is specified, all methods are supported.
+ ///
+ /// @param method HTTP method allowed for the request.
+ void requireHttpMethod(const HttpRequest::Method& method);
+
+ /// @brief Specifies HTTP version allowed.
+ ///
+ /// Allowed HTTP versions must be specified prior to calling @ref create
+ /// method. If no version is specified, all versions are allowed.
+ ///
+ /// @param version Version number allowed for the request.
+ void requireHttpVersion(const HttpVersion& version);
+
+ /// @brief Specifies a required HTTP header for the request.
+ ///
+ /// Required headers must be specified prior to calling @ref create method.
+ /// The specified header must exist in the received HTTP request. This puts
+ /// no requirement on the header value.
+ ///
+ /// @param header_name Required header name.
+ void requireHeader(const std::string& header_name);
+
+ /// @brief Specifies a required value of a header in the request.
+ ///
+ /// Required header values must be specified prior to calling @ref create
+ /// method. The specified header must exist and its value must be equal to
+ /// the value specified as second parameter.
+ ///
+ /// @param header_name HTTP header name.
+ /// @param header_value HTTP header valuae.
+ void requireHeaderValue(const std::string& header_name,
+ const std::string& header_value);
+
+ /// @brief Checks if the body is required for the HTTP request.
+ ///
+ /// Current implementation simply checks if the "Content-Length" header
+ /// is required for the request.
+ ///
+ /// @return true if the body is required for this request.
+ bool requiresBody() const;
+
+ /// @brief Reads parsed request from the @ref HttpRequestContext, validates
+ /// the request and stores parsed information.
+ ///
+ /// This method must be called before retrieving parsed data using accessors
+ /// such as @ref getMethod, @ref getUri etc.
+ ///
+ /// This method doesn't parse the HTTP request body.
+ ///
+ /// @throw HttpRequestError if the parsed request doesn't meet the specified
+ /// requirements for it.
+ virtual void create();
+
+ /// @brief Complete parsing of the HTTP request.
+ ///
+ /// HTTP request parsing is performed in two stages: HTTP headers, then
+ /// request body. The @ref create method parses HTTP headers. Once this is
+ /// done, the caller can check if the "Content-Length" was specified and use
+ /// it's value to determine the size of the body which is parsed in the
+ /// second stage.
+ ///
+ /// This method generally performs the body parsing, but if it determines
+ /// that the @ref create method hasn't been called, it calls @ref create
+ /// before parsing the body.
+ ///
+ /// The derivations must call @ref create if it hasn't been called prior to
+ /// calling this method. It must set @ref finalized_ to true if the call
+ /// to @ref finalize was successful.
+ virtual void finalize();
+
+ /// @brief Reset the state of the object.
+ virtual void reset();
+
+ /// @name HTTP data accessors.
+ ///
+ //@{
+ /// @brief Returns HTTP method of the request.
+ Method getMethod() const;
+
+ /// @brief Returns HTTP request URI.
+ std::string getUri() const;
+
+ /// @brief Returns HTTP version number (major and minor).
+ HttpVersion getHttpVersion() const;
+
+ /// @brief Returns a value of the specified HTTP header.
+ ///
+ /// @param header Name of the HTTP header.
+ ///
+ /// @throw HttpRequestError if the header doesn't exist.
+ std::string getHeaderValue(const std::string& header) const;
+
+ /// @brief Returns a value of the specified HTTP header as number.
+ ///
+ /// @param header Name of the HTTP header.
+ ///
+ /// @throw HttpRequestError if the header doesn't exist or if the
+ /// header value is not number.
+ uint64_t getHeaderValueAsUint64(const std::string& header) const;
+
+ /// @brief Returns HTTP message body as string.
+ std::string getBody() const;
+
+ /// @brief Checks if the request has been successfully finalized.
+ ///
+ /// The request is gets finalized on successful call to
+ /// @ref HttpRequest::finalize.
+ ///
+ /// @return true if the request has been finalized, false otherwise.
+ bool isFinalized() const {
+ return (finalized_);
+ }
+
+ //@}
+
+protected:
+
+ /// @brief Checks if the @ref create was called.
+ ///
+ /// @throw HttpRequestError if @ref create wasn't called.
+ void checkCreated() const;
+
+ /// @brief Checks if the @ref finalize was called.
+ ///
+ /// @throw HttpRequestError if @ref finalize wasn't called.
+ void checkFinalized() const;
+
+ /// @brief Checks if the set is empty or the specified element belongs
+ /// to this set.
+ ///
+ /// This is a convenience method used by the class to verify that the
+ /// given HTTP method belongs to "required methods", HTTP version belongs
+ /// to "required versions" etc.
+ ///
+ /// @param element Reference to the element.
+ /// @param element_set Reference to the set of elements.
+ /// @tparam Element type, e.g. @ref Method, @ref HttpVersion etc.
+ ///
+ /// @return true if the element set is empty or if the element belongs
+ /// to the set.
+ template<typename T>
+ bool inRequiredSet(const T& element,
+ const std::set<T>& element_set) const;
+
+ /// @brief Converts HTTP method specified in textual format to @ref Method.
+ ///
+ /// @param method HTTP method specified in the textual format. This value
+ /// is case insensitive.
+ ///
+ /// @return HTTP method as enum.
+ /// @throw HttpRequestError if unknown method specified.
+ Method methodFromString(std::string method) const;
+
+ /// @brief Converts HTTP method to string.
+ ///
+ /// @param method HTTP method specified as enum.
+ ///
+ /// @return HTTP method as string.
+ std::string methodToString(const HttpRequest::Method& method) const;
+
+ /// @brief Set of required HTTP methods.
+ ///
+ /// If the set is empty, all methods are allowed.
+ std::set<Method> required_methods_;
+
+ /// @brief Set of required HTTP versions.
+ ///
+ /// If the set is empty, all versions are allowed.
+ std::set<HttpVersion> required_versions_;
+
+ /// @brief Map holding required HTTP headers.
+ ///
+ /// The key of this map specifies the HTTP header name. The value
+ /// specifies the HTTP header value. If the value is empty, the
+ /// header is required but the value of the header is not checked.
+ /// If the value is non-empty, the value in the HTTP request must
+ /// be equal to the value in the map.
+ std::map<std::string, std::string> required_headers_;
+
+ /// @brief Flag indicating whether @ref create was called.
+ bool created_;
+
+ /// @brief Flag indicating whether @ref finalize was called.
+ bool finalized_;
+
+ /// @brief HTTP method of the request.
+ Method method_;
+
+ /// @brief Parsed HTTP headers.
+ std::map<std::string, std::string> headers_;
+
+ /// @brief Pointer to the @ref HttpRequestContext holding parsed
+ /// data.
+ HttpRequestContextPtr context_;
+};
+
+}
+}
+
+#endif
diff --git a/src/lib/http/request_context.h b/src/lib/http/request_context.h
new file mode 100644
index 0000000000..bcbacdac43
--- /dev/null
+++ b/src/lib/http/request_context.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_REQUEST_CONTEXT_H
+#define HTTP_REQUEST_CONTEXT_H
+
+#include <http/header_context.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP request context.
+///
+/// The context is used by the @ref HttpRequestParser to store parsed
+/// data. This data is later used to create an instance of the
+/// @ref HttpRequest or its derivation.
+struct HttpRequestContext {
+ /// @brief HTTP request method.
+ std::string method_;
+ /// @brief HTTP request URI.
+ std::string uri_;
+ /// @brief HTTP major version number.
+ unsigned int http_version_major_;
+ /// @brief HTTP minor version number.
+ unsigned int http_version_minor_;
+ /// @brief Collection of HTTP headers.
+ std::vector<HttpHeaderContext> headers_;
+ /// @brief HTTP request body.
+ std::string body_;
+};
+
+/// @brief Pointer to the @ref HttpRequestContext.
+typedef boost::shared_ptr<HttpRequestContext> HttpRequestContextPtr;
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/request_parser.cc b/src/lib/http/request_parser.cc
new file mode 100644
index 0000000000..f77a9eef4b
--- /dev/null
+++ b/src/lib/http/request_parser.cc
@@ -0,0 +1,676 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/request_parser.h>
+#include <boost/bind.hpp>
+#include <cctype>
+#include <iostream>
+
+using namespace isc::util;
+
+namespace isc {
+namespace http {
+
+const int HttpRequestParser::RECEIVE_START_ST;
+const int HttpRequestParser::HTTP_METHOD_ST;
+const int HttpRequestParser::HTTP_URI_ST;
+const int HttpRequestParser::HTTP_VERSION_H_ST;
+const int HttpRequestParser::HTTP_VERSION_T1_ST;
+const int HttpRequestParser::HTTP_VERSION_T2_ST;
+const int HttpRequestParser::HTTP_VERSION_P_ST;
+const int HttpRequestParser::HTTP_VERSION_SLASH_ST;
+const int HttpRequestParser::HTTP_VERSION_MAJOR_START_ST;
+const int HttpRequestParser::HTTP_VERSION_MAJOR_ST;
+const int HttpRequestParser::HTTP_VERSION_MINOR_START_ST;
+const int HttpRequestParser::HTTP_VERSION_MINOR_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE1_ST;
+const int HttpRequestParser::HEADER_LINE_START_ST;
+const int HttpRequestParser::HEADER_LWS_ST;
+const int HttpRequestParser::HEADER_NAME_ST;
+const int HttpRequestParser::SPACE_BEFORE_HEADER_VALUE_ST;
+const int HttpRequestParser::HEADER_VALUE_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE2_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE3_ST;
+const int HttpRequestParser::HTTP_BODY_ST;
+const int HttpRequestParser::HTTP_PARSE_OK_ST;
+const int HttpRequestParser::HTTP_PARSE_FAILED_ST;
+
+const int HttpRequestParser::DATA_READ_OK_EVT;
+const int HttpRequestParser::NEED_MORE_DATA_EVT;
+const int HttpRequestParser::MORE_DATA_PROVIDED_EVT;
+const int HttpRequestParser::HTTP_PARSE_OK_EVT;
+const int HttpRequestParser::HTTP_PARSE_FAILED_EVT;
+
+HttpRequestParser::HttpRequestParser(HttpRequest& request)
+ : StateModel(), buffer_(), request_(request),
+ context_(request_.context()), error_message_() {
+}
+
+void
+HttpRequestParser::initModel() {
+ // Initialize dictionaries of events and states.
+ initDictionaries();
+
+ // Set the current state to starting state and enter the run loop.
+ setState(RECEIVE_START_ST);
+
+ // Parsing starts from here.
+ postNextEvent(START_EVT);
+}
+
+void
+HttpRequestParser::poll() {
+ try {
+ // Run the parser until it runs out of input data or until
+ // parsing completes.
+ do {
+ getState(getCurrState())->run();
+
+ } while (!isModelDone() && (getNextEvent() != NOP_EVT) &&
+ (getNextEvent() != NEED_MORE_DATA_EVT));
+ } catch (const std::exception& ex) {
+ abortModel(ex.what());
+ }
+}
+
+bool
+HttpRequestParser::needData() const {
+ return ((getNextEvent() == NEED_MORE_DATA_EVT) ||
+ (getNextEvent() == START_EVT));
+}
+
+bool
+HttpRequestParser::httpParseOk() const {
+ return ((getNextEvent() == END_EVT) &&
+ (getLastEvent() == HTTP_PARSE_OK_EVT));
+}
+
+void
+HttpRequestParser::postBuffer(const void* buf, const size_t buf_size) {
+ if (buf_size > 0) {
+ // The next event is NEED_MORE_DATA_EVT when the parser wants to
+ // signal that more data is needed. This method is called to supply
+ // more data and thus it should change the next event to
+ // MORE_DATA_PROVIDED_EVT.
+ if (getNextEvent() == NEED_MORE_DATA_EVT) {
+ transition(getCurrState(), MORE_DATA_PROVIDED_EVT);
+ }
+ buffer_.insert(buffer_.end(), static_cast<const char*>(buf),
+ static_cast<const char*>(buf) + buf_size);
+ }
+}
+
+void
+HttpRequestParser::defineEvents() {
+ StateModel::defineEvents();
+
+ // Define HTTP parser specific events.
+ defineEvent(DATA_READ_OK_EVT, "DATA_READ_OK_EVT");
+ defineEvent(NEED_MORE_DATA_EVT, "NEED_MORE_DATA_EVT");
+ defineEvent(MORE_DATA_PROVIDED_EVT, "MORE_DATA_PROVIDED_EVT");
+ defineEvent(HTTP_PARSE_OK_EVT, "HTTP_PARSE_OK_EVT");
+ defineEvent(HTTP_PARSE_FAILED_EVT, "HTTP_PARSE_FAILED_EVT");
+}
+
+void
+HttpRequestParser::verifyEvents() {
+ StateModel::verifyEvents();
+
+ getEvent(DATA_READ_OK_EVT);
+ getEvent(NEED_MORE_DATA_EVT);
+ getEvent(MORE_DATA_PROVIDED_EVT);
+ getEvent(HTTP_PARSE_OK_EVT);
+ getEvent(HTTP_PARSE_FAILED_EVT);
+}
+
+void
+HttpRequestParser::defineStates() {
+ // Call parent class implementation first.
+ StateModel::defineStates();
+
+ // Define HTTP parser specific states.
+ defineState(RECEIVE_START_ST, "RECEIVE_START_ST",
+ boost::bind(&HttpRequestParser::receiveStartHandler, this));
+
+ defineState(HTTP_METHOD_ST, "HTTP_METHOD_ST",
+ boost::bind(&HttpRequestParser::httpMethodHandler, this));
+
+ defineState(HTTP_URI_ST, "HTTP_URI_ST",
+ boost::bind(&HttpRequestParser::uriHandler, this));
+
+ defineState(HTTP_VERSION_H_ST, "HTTP_VERSION_H_ST",
+ boost::bind(&HttpRequestParser::versionHTTPHandler, this, 'H',
+ HTTP_VERSION_T1_ST));
+
+ defineState(HTTP_VERSION_T1_ST, "HTTP_VERSION_T1_ST",
+ boost::bind(&HttpRequestParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_T2_ST));
+
+ defineState(HTTP_VERSION_T2_ST, "HTTP_VERSION_T2_ST",
+ boost::bind(&HttpRequestParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_P_ST));
+
+ defineState(HTTP_VERSION_P_ST, "HTTP_VERSION_P_ST",
+ boost::bind(&HttpRequestParser::versionHTTPHandler, this, 'P',
+ HTTP_VERSION_SLASH_ST));
+
+ defineState(HTTP_VERSION_SLASH_ST, "HTTP_VERSION_SLASH_ST",
+ boost::bind(&HttpRequestParser::versionHTTPHandler, this, '/',
+ HTTP_VERSION_MAJOR_ST));
+
+ defineState(HTTP_VERSION_MAJOR_START_ST, "HTTP_VERSION_MAJOR_START_ST",
+ boost::bind(&HttpRequestParser::versionNumberStartHandler, this,
+ HTTP_VERSION_MAJOR_ST,
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MAJOR_ST, "HTTP_VERSION_MAJOR_ST",
+ boost::bind(&HttpRequestParser::versionNumberHandler, this,
+ '.', HTTP_VERSION_MINOR_START_ST,
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MINOR_START_ST, "HTTP_VERSION_MINOR_START_ST",
+ boost::bind(&HttpRequestParser::versionNumberStartHandler, this,
+ HTTP_VERSION_MINOR_ST,
+ &context_->http_version_minor_));
+
+ defineState(HTTP_VERSION_MINOR_ST, "HTTP_VERSION_MINOR_ST",
+ boost::bind(&HttpRequestParser::versionNumberHandler, this,
+ '\r', EXPECTING_NEW_LINE1_ST,
+ &context_->http_version_minor_));
+
+ defineState(EXPECTING_NEW_LINE1_ST, "EXPECTING_NEW_LINE1_ST",
+ boost::bind(&HttpRequestParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(HEADER_LINE_START_ST, "HEADER_LINE_START_ST",
+ boost::bind(&HttpRequestParser::headerLineStartHandler, this));
+
+ defineState(HEADER_LWS_ST, "HEADER_LWS_ST",
+ boost::bind(&HttpRequestParser::headerLwsHandler, this));
+
+ defineState(HEADER_NAME_ST, "HEADER_NAME_ST",
+ boost::bind(&HttpRequestParser::headerNameHandler, this));
+
+ defineState(SPACE_BEFORE_HEADER_VALUE_ST, "SPACE_BEFORE_HEADER_VALUE_ST",
+ boost::bind(&HttpRequestParser::spaceBeforeHeaderValueHandler, this));
+
+ defineState(HEADER_VALUE_ST, "HEADER_VALUE_ST",
+ boost::bind(&HttpRequestParser::headerValueHandler, this));
+
+ defineState(EXPECTING_NEW_LINE2_ST, "EXPECTING_NEW_LINE2",
+ boost::bind(&HttpRequestParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(EXPECTING_NEW_LINE3_ST, "EXPECTING_NEW_LINE3_ST",
+ boost::bind(&HttpRequestParser::expectingNewLineHandler, this,
+ HTTP_PARSE_OK_ST));
+
+ defineState(HTTP_BODY_ST, "HTTP_BODY_ST",
+ boost::bind(&HttpRequestParser::bodyHandler, this));
+
+ defineState(HTTP_PARSE_OK_ST, "HTTP_PARSE_OK_ST",
+ boost::bind(&HttpRequestParser::parseEndedHandler, this));
+
+ defineState(HTTP_PARSE_FAILED_ST, "HTTP_PARSE_FAILED_ST",
+ boost::bind(&HttpRequestParser::parseEndedHandler, this));
+}
+
+void
+HttpRequestParser::parseFailure(const std::string& error_msg) {
+ error_message_ = error_msg + " : " + getContextStr();
+ transition(HTTP_PARSE_FAILED_ST, HTTP_PARSE_FAILED_EVT);
+}
+
+void
+HttpRequestParser::onModelFailure(const std::string& explanation) {
+ if (error_message_.empty()) {
+ error_message_ = explanation;
+ }
+}
+
+char
+HttpRequestParser::getNextFromBuffer() {
+ unsigned int ev = getNextEvent();
+ char c = '\0';
+ // The caller should always provide additional data when the
+ // NEED_MORE_DATA_EVT occurs. If the next event is still
+ // NEED_MORE_DATA_EVT it indicates that the caller hasn't provided
+ // the data.
+ if (ev == NEED_MORE_DATA_EVT) {
+ isc_throw(HttpRequestParserError,
+ "HTTP request parser requires new data to progress, but no data"
+ " have been provided. The transaction is aborted to avoid"
+ " a deadlock. This is a Kea HTTP server logic error!");
+
+ } else {
+ // Try to pop next character from the buffer.
+ const bool data_exist = popNextFromBuffer(c);
+ if (!data_exist) {
+ // There is no more data so it is really not possible that we're
+ // at MORE_DATA_PROVIDED_EVT.
+ if (ev == MORE_DATA_PROVIDED_EVT) {
+ isc_throw(HttpRequestParserError,
+ "HTTP server state indicates that new data have been"
+ " provided to be parsed, but the transaction buffer"
+ " contains no new data. This is a Kea HTTP server logic"
+ " error!");
+
+ } else {
+ // If there is no more data we should set NEED_MORE_DATA_EVT
+ // event to indicate that new data should be provided.
+ transition(getCurrState(), NEED_MORE_DATA_EVT);
+ }
+ }
+ }
+ return (c);
+}
+
+void
+HttpRequestParser::invalidEventError(const std::string& handler_name,
+ const unsigned int event) {
+ isc_throw(HttpRequestParserError, handler_name << ": "
+ << " invalid event " << getEventLabel(static_cast<int>(event)));
+}
+
+void
+HttpRequestParser::stateWithReadHandler(const std::string& handler_name,
+ boost::function<void(const char c)>
+ after_read_logic) {
+ char c = getNextFromBuffer();
+ // Do nothing if we reached the end of buffer.
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case DATA_READ_OK_EVT:
+ case MORE_DATA_PROVIDED_EVT:
+ after_read_logic(c);
+ break;
+ default:
+ invalidEventError(handler_name, getNextEvent());
+ }
+ }
+}
+
+
+void
+HttpRequestParser::receiveStartHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case START_EVT:
+ // The first byte should contain a first character of the
+ // HTTP method name.
+ if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid first character " + std::string(1, c) +
+ " in HTTP method name");
+
+ } else {
+ context_->method_.push_back(c);
+ transition(HTTP_METHOD_ST, DATA_READ_OK_EVT);
+ }
+ break;
+
+ default:
+ invalidEventError("receiveStartHandler", getNextEvent());
+ }
+ }
+}
+
+void
+HttpRequestParser::httpMethodHandler() {
+ stateWithReadHandler("httpMethodHandler", [this](const char c) {
+ // Space character terminates the HTTP method name. Next thing
+ // is the URI.
+ if (c == ' ') {
+ transition(HTTP_URI_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in HTTP method name");
+
+ } else {
+ // Still parsing the method. Append the next character to the
+ // method name.
+ context_->method_.push_back(c);
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::uriHandler() {
+ stateWithReadHandler("uriHandler", [this](const char c) {
+ // Space character terminates the URI.
+ if (c == ' ') {
+ transition(HTTP_VERSION_H_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in HTTP URI");
+
+ } else {
+ // Still parsing the URI. Append the next character to the
+ // method name.
+ context_->uri_.push_back(c);
+ transition(HTTP_URI_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state) {
+ stateWithReadHandler("versionHTTPHandler",
+ [this, expected_letter, next_state](const char c) {
+ // We're handling one of the letters: 'H', 'T' or 'P'.
+ if (c == expected_letter) {
+ // The HTTP version is specified as "HTTP/X.Y". If the current
+ // character is a slash we're starting to parse major HTTP version
+ // number. Let's reset the version numbers.
+ if (c == '/') {
+ context_->http_version_major_ = 0;
+ context_->http_version_minor_ = 0;
+ }
+ // In all cases, let's transition to next specified state.
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ // Unexpected character found. Parsing fails.
+ parseFailure("unexpected character " + std::string(1, c) +
+ " in HTTP version string");
+ }
+ });
+}
+
+void
+HttpRequestParser::versionNumberStartHandler(const unsigned int next_state,
+ unsigned int* storage) {
+ stateWithReadHandler("versionNumberStartHandler",
+ [this, next_state, storage](const char c) mutable {
+ // HTTP version number must be a digit.
+ if (isdigit(c)) {
+ // Update the version number using new digit being parsed.
+ *storage = *storage * 10 + c - '0';
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ parseFailure("expected digit in HTTP version, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpRequestParser::versionNumberHandler(const char following_character,
+ const unsigned int next_state,
+ unsigned int* const storage) {
+ stateWithReadHandler("versionNumberHandler",
+ [this, following_character, next_state, storage](const char c)
+ mutable {
+ // We're getting to the end of the version number, let's transition
+ // to next state.
+ if (c == following_character) {
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else if (isdigit(c)) {
+ // Current character is a digit, so update the version number.
+ *storage = *storage * 10 + c - '0';
+
+ } else {
+ parseFailure("expected digit in HTTP version, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpRequestParser::expectingNewLineHandler(const unsigned int next_state) {
+ stateWithReadHandler("expectingNewLineHandler", [this, next_state](const char c) {
+ // Only a new line character is allowed in this state.
+ if (c == '\n') {
+ // If next state is HTTP_PARSE_OK_ST it means that we're
+ // parsing 3rd new line in the HTTP request message. This
+ // terminates the HTTP request (if there is no body) or marks the
+ // beginning of the body.
+ if (next_state == HTTP_PARSE_OK_ST) {
+ // Whether there is a body in this message or not, we should
+ // parse the HTTP headers to validate it and to check if there
+ // is "Content-Length" specified. The "Content-Length" is
+ // required for parsing body.
+ request_.create();
+ try {
+ // This will throw exception if there is no Content-Length.
+ uint64_t content_length =
+ request_.getHeaderValueAsUint64("Content-Length");
+ if (content_length > 0) {
+ // There is body in this request, so let's parse it.
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+ }
+ } catch (const std::exception& ex) {
+ // There is no body in this message. If the body is required
+ // parsing fails.
+ if (request_.requiresBody()) {
+ parseFailure("HTTP message lacks a body");
+
+ } else {
+ // Body not required so simply terminate parsing.
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ }
+
+ } else {
+ // This is 1st or 2nd new line, so let's transition to the
+ // next state required by this handler.
+ transition(next_state, DATA_READ_OK_EVT);
+ }
+ } else {
+ parseFailure("expecting new line after CR, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpRequestParser::headerLineStartHandler() {
+ stateWithReadHandler("headerLineStartHandler", [this](const char c) {
+ // If we're parsing HTTP headers and we found CR it marks the
+ // end of headers section.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE3_ST, DATA_READ_OK_EVT);
+
+ } else if (!context_->headers_.empty() && ((c == ' ') || (c == '\t'))) {
+ // New line in headers section followed by space or tab is an LWS,
+ // a line break within header value.
+ transition(HEADER_LWS_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in header name");
+
+ } else {
+ // Update header name with the parse letter.
+ context_->headers_.push_back(HttpHeaderContext());
+ context_->headers_.back().name_.push_back(c);
+ transition(HEADER_NAME_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::headerLwsHandler() {
+ stateWithReadHandler("headerLwsHandler", [this](const char c) {
+ if (c == '\r') {
+ // Found CR during parsing a header value. Next value
+ // should be new line.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if ((c == ' ') || (c == '\t')) {
+ // Space and tab is used to mark LWS. Simply swallow
+ // this character.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header " +
+ context_->headers_.back().name_);
+
+ } else {
+ // We're parsing header value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::headerNameHandler() {
+ stateWithReadHandler("headerNameHandler", [this](const char c) {
+ // Colon follows header name and it has its own state.
+ if (c == ':') {
+ transition(SPACE_BEFORE_HEADER_VALUE_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " found in the HTTP header name");
+
+ } else {
+ // Parsing a header name, so update it.
+ context_->headers_.back().name_.push_back(c);
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::spaceBeforeHeaderValueHandler() {
+ stateWithReadHandler("spaceBeforeHeaderValueHandler", [this](const char c) {
+ if (c == ' ') {
+ // Remove leading whitespace from the header value.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (c == '\r') {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::headerValueHandler() {
+ stateWithReadHandler("headerValueHandler", [this](const char c) {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::bodyHandler() {
+ stateWithReadHandler("bodyHandler", [this](const char c) {
+ // We don't validate the body at this stage. Simply record the
+ // number of characters specified within "Content-Length".
+ context_->body_.push_back(c);
+ if (context_->body_.length() <
+ request_.getHeaderValueAsUint64("Content-Length")) {
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+ } else {
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ });
+}
+
+
+void
+HttpRequestParser::parseEndedHandler() {
+ switch(getNextEvent()) {
+ case HTTP_PARSE_OK_EVT:
+ request_.finalize();
+ transition(END_ST, END_EVT);
+ break;
+ case HTTP_PARSE_FAILED_EVT:
+ abortModel("HTTP request parsing failed");
+ break;
+
+ default:
+ invalidEventError("parseEndedHandler", getNextEvent());
+ }
+}
+
+bool
+HttpRequestParser::popNextFromBuffer(char& next) {
+ // If there are any characters in the buffer, pop next.
+ if (!buffer_.empty()) {
+ next = buffer_.front();
+ buffer_.pop_front();
+ return (true);
+ }
+ return (false);
+}
+
+
+bool
+HttpRequestParser::isChar(const char c) const {
+ // was (c >= 0) && (c <= 127)
+ return (c >= 0);
+}
+
+bool
+HttpRequestParser::isCtl(const char c) const {
+ return (((c >= 0) && (c <= 31)) || (c == 127));
+}
+
+bool
+HttpRequestParser::isSpecial(const char c) const {
+ switch (c) {
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case '@':
+ case ',':
+ case ';':
+ case ':':
+ case '\\':
+ case '"':
+ case '/':
+ case '[':
+ case ']':
+ case '?':
+ case '=':
+ case '{':
+ case '}':
+ case ' ':
+ case '\t':
+ return true;
+
+ default:
+ ;
+ }
+
+ return false;
+}
+
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/request_parser.h b/src/lib/http/request_parser.h
new file mode 100644
index 0000000000..0cbce1e4be
--- /dev/null
+++ b/src/lib/http/request_parser.h
@@ -0,0 +1,463 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_REQUEST_PARSER_H
+#define HTTP_REQUEST_PARSER_H
+
+#include <exceptions/exceptions.h>
+#include <http/request.h>
+#include <util/state_model.h>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <list>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when an error during parsing HTTP request
+/// has occurred.
+///
+/// The most common errors are due to receiving malformed requests.
+class HttpRequestParserError : public Exception {
+public:
+ HttpRequestParserError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class HttpRequestParser;
+
+/// @brief Pointer to the @ref HttpRequestParser.
+typedef boost::shared_ptr<HttpRequestParser> HttpRequestParserPtr;
+
+/// @brief A generic parser for HTTP requests.
+///
+/// This class implements a parser for HTTP requests. The parser derives from
+/// @ref isc::util::StateModel class and implements its own state machine on
+/// top of it. The states of the parser reflect various parts of the HTTP
+/// message being parsed, e.g. parsing HTTP method, parsing URI, parsing
+/// message body etc. The descriptions of all parser states are provided
+/// below together with the constants defining these states.
+///
+/// HTTP uses TCP as a transport which is asynchronous in nature, i.e. the
+/// HTTP message is received in chunks and multiple TCP connections can be
+/// established at the same time. Multiplexing between these connections
+/// requires providing a separate state machine per connection to "remember"
+/// the state of each transaction when the parser is waiting for asynchronous
+/// data to be delivered. While the parser is waiting for the data, it can
+/// parse requests received over other connections. This class provides means
+/// for parsing partial data received over the specific connection and
+/// interrupting data parsing to switch to a different context.
+///
+/// The request parser validates the syntax of the received message as it
+/// progresses with parsing the data. Though, it doesn't interpret the received
+/// data until the whole message is parsed. In most cases we want to apply some
+/// restrictions on the message content, e.g. Kea Control API requires that
+/// commands are sent using HTTP POST, with a JSON command being carried in a
+/// message body. The parser doesn't verify if the message meets these
+/// restrictions until the whole message is parsed, i.e. stored in the
+/// @ref HttpRequestContext object. This object is associated with a
+/// @ref HttpRequest object (or its derivation). When the parsing is completed,
+/// the @ref HttpRequest::create method is called to retrieve the data from
+/// the @ref HttpRequestContext and interpret the data. In particular, the
+/// @ref HttpRequest or its derivation checks if the received message meets
+/// desired restrictions.
+///
+/// Kea Control API uses @ref PostHttpRequestJson class (which derives from the
+/// @ref HttpRequest) to interpret received request. This class requires
+/// that the HTTP request uses POST method and contains the following headers:
+/// - Content-Type: application/json,
+/// - Content-Length
+///
+/// If any of these restrictions is not met in the received message, an
+/// exception will be thrown, thereby @ref HttpRequestParser will fail parsing
+/// the message.
+///
+/// A new method @ref HttpRequestParser::poll has been created to run the
+/// parser's state machine as long as there are unparsed data in the parser's
+/// internal buffer. This method returns control to the caller when the parser
+/// runs out of data in this buffer. The caller must feed the buffer by calling
+/// @ref HttpRequestParser::postBuffer and then run @ref HttpRequestParser::poll
+/// again.
+///
+/// In case the caller provides more data than indicated by the "Content-Length"
+/// header the parser will return from poll() after parsing the data which
+/// constitute the HTTP request and not parse the extraneous data. The caller
+/// should test the @ref HttpRequestParser::needData and
+/// @ref HttpRequestParser::httpParseOk to determine whether parsing has
+/// completed.
+///
+/// The @ref util::StateModel::runModel must not be used to run the
+/// @ref HttpRequestParser state machine, thus it is made private method.
+class HttpRequestParser : public util::StateModel {
+public:
+
+ /// @name States supported by the HttpRequestParser.
+ ///
+ //@{
+
+ /// @brief State indicating a beginning of parsing.
+ static const int RECEIVE_START_ST = SM_DERIVED_STATE_MIN + 1;
+
+ /// @brief Parsing HTTP method, e.g. GET, POST etc.
+ static const int HTTP_METHOD_ST = SM_DERIVED_STATE_MIN + 2;
+
+ /// @brief Parsing URI.
+ static const int HTTP_URI_ST = SM_DERIVED_STATE_MIN + 3;
+
+ /// @brief Parsing letter "H" of "HTTP".
+ static const int HTTP_VERSION_H_ST = SM_DERIVED_STATE_MIN + 4;
+
+ /// @brief Parsing first occurrence of "T" in "HTTP".
+ static const int HTTP_VERSION_T1_ST = SM_DERIVED_STATE_MIN + 5;
+
+ /// @brief Parsing second occurrence of "T" in "HTTP".
+ static const int HTTP_VERSION_T2_ST = SM_DERIVED_STATE_MIN + 6;
+
+ /// @brief Parsing letter "P" in "HTTP".
+ static const int HTTP_VERSION_P_ST = SM_DERIVED_STATE_MIN + 7;
+
+ /// @brief Parsing slash character in "HTTP/Y.X"
+ static const int HTTP_VERSION_SLASH_ST = SM_DERIVED_STATE_MIN + 8;
+
+ /// @brief Starting to parse major HTTP version number.
+ static const int HTTP_VERSION_MAJOR_START_ST = SM_DERIVED_STATE_MIN + 9;
+
+ /// @brief Parsing major HTTP version number.
+ static const int HTTP_VERSION_MAJOR_ST = SM_DERIVED_STATE_MIN + 10;
+
+ /// @brief Starting to parse minor HTTP version number.
+ static const int HTTP_VERSION_MINOR_START_ST = SM_DERIVED_STATE_MIN + 11;
+
+ /// @brief Parsing minor HTTP version number.
+ static const int HTTP_VERSION_MINOR_ST = SM_DERIVED_STATE_MIN + 12;
+
+ /// @brief Parsing first new line (after HTTP version number).
+ static const int EXPECTING_NEW_LINE1_ST = SM_DERIVED_STATE_MIN + 13;
+
+ /// @brief Starting to parse a header line.
+ static const int HEADER_LINE_START_ST = SM_DERIVED_STATE_MIN + 14;
+
+ /// @brief Parsing LWS (Linear White Space), i.e. new line with a space
+ /// or tab character while parsing a HTTP header.
+ static const int HEADER_LWS_ST = SM_DERIVED_STATE_MIN + 15;
+
+ /// @brief Parsing header name.
+ static const int HEADER_NAME_ST = SM_DERIVED_STATE_MIN + 16;
+
+ /// @brief Parsing space before header value.
+ static const int SPACE_BEFORE_HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 17;
+
+ /// @brief Parsing header value.
+ static const int HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 18;
+
+ /// @brief Expecting new line after parsing header value.
+ static const int EXPECTING_NEW_LINE2_ST = SM_DERIVED_STATE_MIN + 19;
+
+ /// @brief Expecting second new line marking end of HTTP headers.
+ static const int EXPECTING_NEW_LINE3_ST = SM_DERIVED_STATE_MIN + 20;
+
+ /// @brief Parsing body of a HTTP message.
+ static const int HTTP_BODY_ST = SM_DERIVED_STATE_MIN + 21;
+
+ /// @brief Parsing successfully completed.
+ static const int HTTP_PARSE_OK_ST = SM_DERIVED_STATE_MIN + 100;
+
+ /// @brief Parsing failed.
+ static const int HTTP_PARSE_FAILED_ST = SM_DERIVED_STATE_MIN + 101;
+
+ //@}
+
+
+ /// @name Events used during HTTP message parsing.
+ ///
+ //@{
+
+ /// @brief Chunk of data successfully read and parsed.
+ static const int DATA_READ_OK_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+ /// @brief Unable to proceed with parsing until new data is provided.
+ static const int NEED_MORE_DATA_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+ /// @brief New data provided and parsing should continue.
+ static const int MORE_DATA_PROVIDED_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+ /// @brief Parsing HTTP request successful.
+ static const int HTTP_PARSE_OK_EVT = SM_DERIVED_EVENT_MIN + 100;
+
+ /// @brief Parsing HTTP request failed.
+ static const int HTTP_PARSE_FAILED_EVT = SM_DERIVED_EVENT_MIN + 101;
+
+ //@}
+
+ /// @brief Constructor.
+ ///
+ /// Creates new instance of the parser.
+ ///
+ /// @param request Reference to the @ref HttpRequest object or its
+ /// derivation that should be used to validate the parsed request and
+ /// to be used as a container for the parsed request.
+ HttpRequestParser(HttpRequest& request);
+
+ /// @brief Initialize the state model for parsing.
+ ///
+ /// This method must be called before parsing the request, i.e. before
+ /// calling @ref HttpRequestParser::poll. It initializes dictionaries of
+ /// states and events, and sets the initial model state to RECEIVE_START_ST.
+ void initModel();
+
+ /// @brief Run the parser as long as the amount of data is sufficient.
+ ///
+ /// The data to be parsed should be provided by calling
+ /// @ref HttpRequestParser::postBuffer. When the parser reaches the end of
+ /// the data buffer the @ref HttpRequestParser::poll sets the next event to
+ /// @ref NEED_MORE_DATA_EVT and returns. The caller should then invoke
+ /// @ref HttpRequestParser::postBuffer again to provide more data to the
+ /// parser, and call @ref HttpRequestParser::poll to continue parsing.
+ ///
+ /// This method also returns when parsing completes or fails. The last
+ /// event can be examined to check whether parsing was successful or not.
+ void poll();
+
+ /// @brief Returns true if the parser needs more data to continue.
+ ///
+ /// @return true if the next event is NEED_MORE_DATA_EVT.
+ bool needData() const;
+
+ /// @brief Returns true if a request has been parsed successfully.
+ bool httpParseOk() const;
+
+ /// @brief Returns error message.
+ std::string getErrorMessage() const {
+ return (error_message_);
+ }
+
+ /// @brief Provides more input data to the parser.
+ ///
+ /// This method must be called prior to calling @ref HttpRequestParser::poll
+ /// to deliver data to be parsed. HTTP requests are received over TCP and
+ /// multiple reads may be necessary to retrieve the entire request. There is
+ /// no need to accumulate the entire request to start parsing it. A chunk
+ /// of data can be provided to the parser using this method and parsed right
+ /// away using @ref HttpRequestParser::poll.
+ ///
+ /// @param buf A pointer to the buffer holding the data.
+ /// @param buf_size Size of the data within the buffer.
+ void postBuffer(const void* buf, const size_t buf_size);
+
+private:
+
+ /// @brief Make @ref runModel private to make sure that the caller uses
+ /// @ref poll method instead.
+ using StateModel::runModel;
+
+ /// @brief Define events used by the parser.
+ virtual void defineEvents();
+
+ /// @brief Verifies events used by the parser.
+ virtual void verifyEvents();
+
+ /// @brief Defines states of the parser.
+ virtual void defineStates();
+
+ /// @brief Transition parser to failure state.
+ ///
+ /// This method transitions the parser to @ref HTTP_PARSE_FAILED_ST and
+ /// sets next event to HTTP_PARSE_FAILED_EVT.
+ ///
+ /// @param error_msg Error message explaining the failure.
+ void parseFailure(const std::string& error_msg);
+
+ /// @brief A method called when parsing fails.
+ ///
+ /// @param explanation Error message explaining the reason for parsing
+ /// failure.
+ virtual void onModelFailure(const std::string& explanation);
+
+ /// @brief Retrieves next byte of data from the buffer.
+ ///
+ /// During normal operation, when there is no more data in the buffer,
+ /// the parser sets NEED_MORE_DATA_EVT as next event to signal the need for
+ /// calling @ref HttpRequestParser::postBuffer.
+ ///
+ /// @throw HttpRequestParserError If current event is already set to
+ /// NEED_MORE_DATA_EVT or MORE_DATA_PROVIDED_EVT. In the former case, it
+ /// indicates that the caller failed to provide new data using
+ /// @ref HttpRequestParser::postBuffer. The latter case is highly unlikely
+ /// as it indicates that no new data were provided but the state of the
+ /// parser was changed from NEED_MORE_DATA_EVT or the data were provided
+ /// but the data buffer is empty. In both cases, it is an internal server
+ /// error.
+ char getNextFromBuffer();
+
+ /// @brief This method is called when invalid event occurred in a particular
+ /// parser state.
+ ///
+ /// This method simply throws @ref HttpRequestParserError informing about
+ /// invalid event occurring for the particular parser state. The error
+ /// message includes the name of the handler in which the exception
+ /// has been thrown. It also includes the event which caused the
+ /// exception.
+ ///
+ /// @param handler_name Name of the handler in which the exception is
+ /// thrown.
+ /// @param event An event which caused the exception.
+ ///
+ /// @throw HttpRequestParserError.
+ void invalidEventError(const std::string& handler_name,
+ const unsigned int event);
+
+ /// @brief Generic parser handler which reads a single byte of data and
+ /// parses it using specified callback function.
+ ///
+ /// This generic handler is used in most of the parser states to parse a
+ /// single byte of input data. If there is no more data it simply returns.
+ /// Otherwise, if the next event is DATA_READ_OK_EVT or
+ /// MORE_DATA_PROVIDED_EVT, it calls the provided callback function to
+ /// parse the new byte of data. For all other states it throws an exception.
+ ///
+ /// @param handler_name Name of the handler function which called this
+ /// method.
+ /// @param after_read_logic Callback function to parse the byte of data.
+ /// This callback function implements state specific logic.
+ ///
+ /// @throw HttpRequestParserError when invalid event occurred.
+ void stateWithReadHandler(const std::string& handler_name,
+ boost::function<void(const char c)>
+ after_read_logic);
+
+ /// @name State handlers.
+ ///
+ //@{
+
+ /// @brief Handler for RECEIVE_START_ST.
+ void receiveStartHandler();
+
+ /// @brief Handler for HTTP_METHOD_ST.
+ void httpMethodHandler();
+
+ /// @brief Handler for HTTP_URI_ST.
+ void uriHandler();
+
+ /// @brief Handler for states parsing "HTTP" string within the first line
+ /// of the HTTP request.
+ ///
+ /// @param expected_letter One of the 'H', 'T', 'P'.
+ /// @param next_state A state to which the parser should transition after
+ /// parsing the character.
+ void versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state);
+
+ /// @brief Handler for HTTP_VERSION_MAJOR_START_ST and
+ /// HTTP_VERSION_MINOR_START_ST.
+ ///
+ /// This handler calculates version number using the following equation:
+ /// @code
+ /// storage = storage * 10 + c - '0';
+ /// @endcode
+ ///
+ /// @param next_state State to which the parser should transition.
+ /// @param [out] storage Reference to a number holding current product of
+ /// parsing major or minor version number.
+ void versionNumberStartHandler(const unsigned int next_state,
+ unsigned int* storage);
+
+ /// @brief Handler for HTTP_VERSION_MAJOR_ST and HTTP_VERSION_MINOR_ST.
+ ///
+ /// This handler calculates version number using the following equation:
+ /// @code
+ /// storage = storage * 10 + c - '0';
+ /// @endcode
+ ///
+ /// @param following_character Character following the version number, i.e.
+ /// '.' for major version, \r for minor version.
+ /// @param next_state State to which the parser should transition.
+ /// @param [out] storage Pointer to a number holding current product of
+ /// parsing major or minor version number.
+ void versionNumberHandler(const char following_character,
+ const unsigned int next_state,
+ unsigned int* const storage);
+
+ /// @brief Handler for states related to new lines.
+ ///
+ /// If the next_state is HTTP_PARSE_OK_ST it indicates that the parsed
+ /// value is a 3rd new line within request HTTP message. In this case the
+ /// handler calls @ref HttpRequest::create to validate the received message
+ /// (excluding body). The hander then reads the "Content-Length" header to
+ /// check if the request contains a body. If the "Content-Length" is greater
+ /// than zero, the parser transitions to HTTP_BODY_ST. If the
+ /// "Content-Length" doesn't exist the parser transitions to
+ /// HTTP_PARSE_OK_ST.
+ ///
+ /// @param next_state A state to which parser should transition.
+ void expectingNewLineHandler(const unsigned int next_state);
+
+ /// @brief Handler for HEADER_LINE_START_ST.
+ void headerLineStartHandler();
+
+ /// @brief Handler for HEADER_LWS_ST.
+ void headerLwsHandler();
+
+ /// @brief Handler for HEADER_NAME_ST.
+ void headerNameHandler();
+
+ /// @brief Handler for SPACE_BEFORE_HEADER_VALUE_ST.
+ void spaceBeforeHeaderValueHandler();
+
+ /// @brief Handler for HEADER_VALUE_ST.
+ void headerValueHandler();
+
+ /// @brief Handler for HTTP_BODY_ST.
+ void bodyHandler();
+
+ /// @brief Handler for HTTP_PARSE_OK_ST and HTTP_PARSE_FAILED_ST.
+ ///
+ /// If parsing is successful, it calls @ref HttpRequest::create to validate
+ /// the HTTP request. In both cases it transitions the parser to the END_ST.
+ void parseEndedHandler();
+
+ /// @brief Tries to read next byte from buffer.
+ ///
+ /// @param [out] next A reference to the variable where read data should be
+ /// stored.
+ ///
+ /// @return true if character was successfully read, false otherwise.
+ bool popNextFromBuffer(char& next);
+
+ /// @brief Checks if specified value is a character.
+ ///
+ /// @return true, if specified value is a character.
+ bool isChar(const char c) const;
+
+ /// @brief Checks if specified value is a control value.
+ ///
+ /// @return true, if specified value is a control value.
+ bool isCtl(const char c) const;
+
+ /// @brief Checks if specified value is a special character.
+ ///
+ /// @return true, if specified value is a special character.
+ bool isSpecial(const char c) const;
+
+ /// @brief Internal buffer from which parser reads data.
+ std::list<char> buffer_;
+
+ /// @brief Reference to the request object specified in the constructor.
+ HttpRequest& request_;
+
+ /// @brief Pointer to the internal context of the @ref HttpRequest object.
+ HttpRequestContextPtr context_;
+
+ /// @brief Error message set by @ref onModelFailure.
+ std::string error_message_;
+};
+
+} // namespace http
+} // namespace isc
+
+#endif // HTTP_REQUEST_PARSER_H
+
diff --git a/src/lib/http/response.cc b/src/lib/http/response.cc
new file mode 100644
index 0000000000..5764b3c300
--- /dev/null
+++ b/src/lib/http/response.cc
@@ -0,0 +1,145 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/date_time.h>
+#include <http/response.h>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <boost/date_time/time_facet.hpp>
+#include <sstream>
+
+using namespace boost::local_time;
+using namespace isc::http;
+
+namespace {
+
+/// @brief A map of status codes to status names.
+const std::map<HttpStatusCode, std::string> status_code_to_description = {
+ { HttpStatusCode::OK, "OK" },
+ { HttpStatusCode::CREATED, "Created" },
+ { HttpStatusCode::ACCEPTED, "Accepted" },
+ { HttpStatusCode::NO_CONTENT, "No Content" },
+ { HttpStatusCode::MULTIPLE_CHOICES, "Multiple Choices" },
+ { HttpStatusCode::MOVED_PERMANENTLY, "Moved Permanently" },
+ { HttpStatusCode::MOVED_TEMPORARILY, "Moved Temporarily" },
+ { HttpStatusCode::NOT_MODIFIED, "Not Modified" },
+ { HttpStatusCode::BAD_REQUEST, "Bad Request" },
+ { HttpStatusCode::UNAUTHORIZED, "Unauthorized" },
+ { HttpStatusCode::FORBIDDEN, "Forbidden" },
+ { HttpStatusCode::NOT_FOUND, "Not Found" },
+ { HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout" },
+ { HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error" },
+ { HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented" },
+ { HttpStatusCode::BAD_GATEWAY, "Bad Gateway" },
+ { HttpStatusCode::SERVICE_UNAVAILABLE, "Service Unavailable" }
+};
+
+/// @brief New line (CRLF).
+const std::string crlf = "\r\n";
+
+}
+
+namespace isc {
+namespace http {
+
+HttpResponse::HttpResponse(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body)
+ : http_version_(version), status_code_(status_code), headers_(),
+ body_() {
+ if (generic_body.set_) {
+ // This currently does nothing, but it is useful to have it here as
+ // an example how to implement it in the derived classes.
+ setGenericBody(status_code);
+ }
+}
+
+void
+HttpResponse::setBody(const std::string& body) {
+ body_ = body;
+}
+
+bool
+HttpResponse::isClientError(const HttpStatusCode& status_code) {
+ // Client errors have status codes of 4XX.
+ uint16_t c = statusCodeToNumber(status_code);
+ return ((c >= 400) && (c < 500));
+}
+
+bool
+HttpResponse::isServerError(const HttpStatusCode& status_code) {
+ // Server errors have status codes of 5XX.
+ uint16_t c = statusCodeToNumber(status_code);
+ return ((c >= 500) && (c < 600));
+}
+
+std::string
+HttpResponse::statusCodeToString(const HttpStatusCode& status_code) {
+ auto status_code_it = status_code_to_description.find(status_code);
+ if (status_code_it == status_code_to_description.end()) {
+ isc_throw(HttpResponseError, "internal server error: no HTTP status"
+ " description for the given status code "
+ << static_cast<uint16_t>(status_code));
+ }
+ return (status_code_it->second);
+}
+
+uint16_t
+HttpResponse::statusCodeToNumber(const HttpStatusCode& status_code) {
+ return (static_cast<uint16_t>(status_code));
+}
+
+std::string
+HttpResponse::getDateHeaderValue() const {
+ // This returns current time in the recommended format.
+ HttpDateTime date_time;
+ return (date_time.rfc1123Format());
+}
+
+std::string
+HttpResponse::toBriefString() const {
+ std::ostringstream s;
+ // HTTP version number and status code.
+ s << "HTTP/" << http_version_.major_ << "." << http_version_.minor_;
+ s << " " << static_cast<uint16_t>(status_code_);
+ s << " " << statusCodeToString(status_code_) << crlf;
+ return (s.str());
+}
+
+std::string
+HttpResponse::toString() const {
+ std::ostringstream s;
+ // HTTP version number and status code.
+ s << toBriefString();
+
+ // We need to at least insert "Date" header into the HTTP headers. This
+ // method is const thus we can't insert it into the headers_ map. We'll
+ // work on the copy of the map. Admittedly, we could just append "Date"
+ // into the generated string but we prefer that headers are ordered
+ // alphabetically.
+ std::map<std::string, std::string> headers(headers_);
+
+ // Update or add "Date" header.
+ addHeaderInternal("Date", getDateHeaderValue(), headers);
+
+ // Always add "Content-Length", perhaps equal to 0.
+ addHeaderInternal("Content-Length", body_.length(), headers);
+
+ // Include all headers.
+ for (auto header = headers.cbegin(); header != headers.cend();
+ ++header) {
+ s << header->first << ": " << header->second << crlf;
+ }
+
+ s << crlf;
+
+ // Include message body.
+ s << body_;
+
+ return (s.str());
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/response.h b/src/lib/http/response.h
new file mode 100644
index 0000000000..a69ecd87ae
--- /dev/null
+++ b/src/lib/http/response.h
@@ -0,0 +1,242 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_H
+#define HTTP_RESPONSE_H
+
+#include <exceptions/exceptions.h>
+#include <http/http_types.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpResponse class.
+class HttpResponseError : public Exception {
+public:
+ HttpResponseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief HTTP status codes (cf RFC 2068)
+enum class HttpStatusCode : std::uint16_t {
+ OK = 200,
+ CREATED = 201,
+ ACCEPTED = 202,
+ NO_CONTENT = 204,
+ MULTIPLE_CHOICES = 300,
+ MOVED_PERMANENTLY = 301,
+ MOVED_TEMPORARILY = 302,
+ NOT_MODIFIED = 304,
+ BAD_REQUEST = 400,
+ UNAUTHORIZED = 401,
+ FORBIDDEN = 403,
+ NOT_FOUND = 404,
+ REQUEST_TIMEOUT = 408,
+ INTERNAL_SERVER_ERROR = 500,
+ NOT_IMPLEMENTED = 501,
+ BAD_GATEWAY = 502,
+ SERVICE_UNAVAILABLE = 503
+};
+
+/// @brief Encapsulates the boolean value indicating if the @ref HttpResponse
+/// constructor should call its @c setGenericBody method during construction.
+struct CallSetGenericBody {
+
+ /// @brief Constructor.
+ ///
+ /// @param set A boolean value indicating if the method should be called
+ /// or not.
+ explicit CallSetGenericBody(const bool set)
+ : set_(set) {
+ }
+
+ /// @brief Returns encapsulated true.
+ static const CallSetGenericBody& yes() {
+ static CallSetGenericBody yes(true);
+ return (yes);
+ }
+
+ /// @brief Returns encapsulated false.
+ static const CallSetGenericBody& no() {
+ static CallSetGenericBody no(false);
+ return (no);
+ }
+
+ /// @brief A storage for the boolean flag.
+ bool set_;
+};
+
+class HttpResponse;
+
+/// @brief Pointer to the @ref HttpResponse object.
+typedef boost::shared_ptr<HttpResponse> HttpResponsePtr;
+
+/// @brief Pointer to the const @ref HttpResponse object.
+typedef boost::shared_ptr<const HttpResponse> ConstHttpResponsePtr;
+
+/// @brief Represents HTTP response message.
+///
+/// This class represents HTTP response message. An instance of this object
+/// or its derivation is typically created by the implementation of the
+/// @ref HttpResponseCreator interface.
+///
+/// It contains @c toString method generating a textual representation of
+/// the HTTP response, which is send to the client over TCP socket.
+class HttpResponse {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates basic instance of the object. It sets the HTTP version and the
+ /// status code to be included in the response.
+ ///
+ /// @param version HTTP version.
+ /// @param status_code HTTP status code.
+ /// @param generic_body Indicates if the constructor should call
+ /// @c setGenericBody to create a generic content for the given
+ /// status code. This should be set to "no" when the constructor is
+ /// called by the derived class which provides its own implementation
+ /// of the @c setGenericBody method.
+ explicit HttpResponse(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body =
+ CallSetGenericBody::yes());
+
+ /// @brief Destructor.
+ ///
+ /// A class having virtual methods must have a virtual destructor.
+ virtual ~HttpResponse() { }
+
+ /// @brief Adds HTTP header to the response.
+ ///
+ /// The "Content-Length" and "Date" headers should not be added using this
+ /// method because they are generated and added automatically when the
+ /// @c toString is called.
+ ///
+ /// @param name Header name.
+ /// @param value Header value.
+ /// @tparam ValueType Type of the header value.
+ template<typename ValueType>
+ void addHeader(const std::string& name, const ValueType& value) {
+ addHeaderInternal(name, value, headers_);
+ }
+
+ /// @brief Assigns body/content to the message.
+ ///
+ /// @param body Body to be assigned.
+ void setBody(const std::string& body);
+
+ /// @brief Checks if the status code indicates client error.
+ ///
+ /// @param status_code HTTP status code.
+ /// @return true if the status code indicates client error.
+ static bool isClientError(const HttpStatusCode& status_code);
+
+ /// @brief Checks if the status code indicates server error.
+ ///
+ /// @param status_code HTTP status code.
+ /// @return true if the status code indicates server error.
+ static bool isServerError(const HttpStatusCode& status_code);
+
+ /// @brief Converts status code to string.
+ ///
+ /// @param status_code HTTP status code.
+ /// @return Textual representation of the status code.
+ static std::string statusCodeToString(const HttpStatusCode& status_code);
+
+ /// @brief Returns HTTP version and HTTP status as a string.
+ std::string toBriefString() const;
+
+ /// @brief Returns textual representation of the HTTP response.
+ ///
+ /// It includes the "Date" header with the current time in RFC 1123 format.
+ /// It also includes "Content-Length" when the response has a non-empty
+ /// body.
+ ///
+ /// @return Textual representation of the HTTP response.
+ std::string toString() const ;
+
+protected:
+
+ /// @brief Adds HTTP header to the map.
+ ///
+ /// @param name Header name.
+ /// @param value Header value.
+ /// @param [out] headers A map to which header value should be inserted.
+ /// @tparam ValueType Type of the header value.
+ template<typename ValueType>
+ void addHeaderInternal(const std::string& name, const ValueType& value,
+ std::map<std::string, std::string>& headers) const {
+ try {
+ headers[name] = boost::lexical_cast<std::string>(value);
+
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(HttpResponseError, "unable to convert the "
+ << name << " header value to a string");
+ }
+ }
+
+ /// @brief Returns current time formatted as required by RFC 1123.
+ ///
+ /// This method is virtual so as it can be overridden in unit tests
+ /// to return a "predictable" value of time, e.g. constant value.
+ ///
+ /// @return Current time formatted as required by RFC 1123.
+ virtual std::string getDateHeaderValue() const;
+
+ /// @brief Convenience method converting status code to numeric value.
+ ///
+ /// @param status_code Status code represented as enum.
+ /// @return Numeric representation of the status code.
+ static uint16_t statusCodeToNumber(const HttpStatusCode& status_code);
+
+private:
+
+ /// @brief Sets generic body for the given status code.
+ ///
+ /// Most of the classes derived from @ref HttpResponse will expect
+ /// a certain content type. Depending on the content type used they
+ /// will use different body formats for error messages. For example,
+ /// a response using text/html will use HTML within the response
+ /// body. The application/json will use JSON body etc. There is a
+ /// need to implement class specific way of generating the body
+ /// for error messages. Thus, each derivation of this class is
+ /// required to implement class specific @ref setGenericBody function
+ /// which should be called in the class constructor.
+ ///
+ /// This is also the case for this class, though the implementation
+ /// of @c setGenericBody is currently no-op.
+ ///
+ /// Note that this class can't be declared virtual because it is
+ /// meant to be called from the class constructor.
+ ///
+ /// @param status_code Status code for which the body should be
+ /// generated.
+ void setGenericBody(const HttpStatusCode& /*status_code*/) { };
+
+ /// @brief Holds HTTP version for the response.
+ HttpVersion http_version_;
+
+ /// @brief Holds status code for the response.
+ HttpStatusCode status_code_;
+
+ /// @brief Holds HTTP headers for the response.
+ std::map<std::string, std::string> headers_;
+
+ /// @brief Holds the body/content.
+ std::string body_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/response_creator.cc b/src/lib/http/response_creator.cc
new file mode 100644
index 0000000000..8c63fea41b
--- /dev/null
+++ b/src/lib/http/response_creator.cc
@@ -0,0 +1,31 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/response_creator.h>
+
+namespace isc {
+namespace http {
+
+HttpResponsePtr
+HttpResponseCreator::createHttpResponse(const ConstHttpRequestPtr& request) {
+ // This should never happen. This method must only be called with a
+ // non null request, so we consider it unlikely internal server error.
+ if (!request) {
+ isc_throw(HttpResponseError, "internal server error: HTTP request is null");
+ }
+
+ // If not finalized, the request parsing failed. Generate HTTP 400.
+ if (!request->isFinalized()) {
+ return (createStockHttpResponse(request, HttpStatusCode::BAD_REQUEST));
+ }
+
+ // Message has been successfully parsed. Create implementation specific
+ // response to this request.
+ return (createDynamicHttpResponse(request));
+}
+
+}
+}
diff --git a/src/lib/http/response_creator.h b/src/lib/http/response_creator.h
new file mode 100644
index 0000000000..dbae326ba9
--- /dev/null
+++ b/src/lib/http/response_creator.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_CREATOR_H
+#define HTTP_RESPONSE_CREATOR_H
+
+#include <http/request.h>
+#include <http/response.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+class HttpResponseCreator;
+
+/// @brief Pointer to the @ref HttpResponseCreator object.
+typedef boost::shared_ptr<HttpResponseCreator> HttpResponseCreatorPtr;
+
+/// @brief Specifies an interface for classes creating HTTP responses
+/// from HTTP requests.
+///
+/// HTTP is designed to carry various content types. Most commonly
+/// this is text/html. In Kea, the application/json content type is used
+/// to carry control commands in JSON format. The libkea-http library is
+/// meant to be generic and provide means for transferring different types
+/// of content, depending on the use case.
+///
+/// This abstract class specifies a common interface for generating HTTP
+/// responses from HTTP requests using specific content type and being
+/// used in some specific context. Kea modules providing HTTP services need to
+/// implement their specific derivations of the @ref HttpResponseCreator
+/// class. These derivations use classes derived from @ref HttpRequest as
+/// an input and classes derived from @ref HttpResponse as an output of
+/// @c createHttpResponse method.
+class HttpResponseCreator {
+public:
+
+ /// @brief Destructor.
+ ///
+ /// Classes with virtual functions need virtual destructors.
+ virtual ~HttpResponseCreator() { };
+
+ /// @brief Create HTTP response from HTTP request received.
+ ///
+ /// This class implements a generic logic for creating a HTTP response.
+ /// Derived classes do not override this method. They merely implement
+ /// the methods it calls.
+ ///
+ /// The request processing may generally fail at one of the two stages:
+ /// parsing or interpretation of the parsed request. During the former
+ /// stage the request's syntax is checked, i.e. HTTP version, URI,
+ /// headers etc. During the latter stage the HTTP server checks if the
+ /// request is valid within the specific context, e.g. valid HTTP version
+ /// used, expected content type etc.
+ ///
+ /// In the @ref HttpRequest terms, the request has gone through the
+ /// first stage if it is "finalized" (see @ref HttpRequest::finalize).
+ /// This method accepts instances of both finalized and not finalized
+ /// requests. If the request isn't finalized it indicates that
+ /// the request parsing has failed. In such case, this method calls
+ /// @c createStockBadRequest to generate a response with HTTP 400 status
+ /// code. If the request is finalized, this method calls
+ /// @c createDynamicHttpResponse to generate implementation specific
+ /// response to the received request.
+ ///
+ /// This method is marked virtual final to prevent derived classes from
+ /// overriding this method. Instead, the derived classes must implement
+ /// protected methods which this method calls.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @return Pointer to the object encapsulating generated HTTP response.
+ /// @throw HttpResponseError if request is a NULL pointer.
+ virtual HttpResponsePtr
+ createHttpResponse(const ConstHttpRequestPtr& request) final;
+
+ /// @brief Create a new request.
+ ///
+ /// This method creates an instance of the @ref HttpRequest or derived
+ /// class. The type of the object is compatible with the instance of
+ /// the @ref HttpResponseCreator implementation which creates it, i.e.
+ /// can be used as an argument in the call to @ref createHttpResponse.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const = 0;
+
+ /// @brief Creates implementation specific HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @param status_code Status code of the response.
+ /// @return Pointer to an object representing HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const ConstHttpRequestPtr& request,
+ const HttpStatusCode& status_code) const = 0;
+
+protected:
+
+ /// @brief Creates implementation specific HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @return Pointer to an object representing HTTP response.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(const ConstHttpRequestPtr& request) = 0;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/response_creator_factory.h b/src/lib/http/response_creator_factory.h
new file mode 100644
index 0000000000..c2fc9178a5
--- /dev/null
+++ b/src/lib/http/response_creator_factory.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_CREATOR_FACTORY_H
+#define HTTP_RESPONSE_CREATOR_FACTORY_H
+
+#include <http/response_creator.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+/// @brief Specifies the interface for implementing custom factory classes
+/// used to create instances of @ref HttpResponseCreator.
+///
+/// The @ref HttpResponseCreator defines an interface for the classes used
+/// to generate HTTP responses. Such classes are defined outside of this
+/// library and they are specific to the needs of the particular module.
+/// In some cases it may be desired to create new instance of the
+/// @ref HttpResponseCreator implementation for every request processed.
+/// The @ref HttpResponseCreatorFactory is an interface to the "factory"
+/// class which generates canned @ref HttpResponseCreator instances. The
+/// pointer to the factory class is passed to the @ref HttpListener and
+/// the listener propagates it down to other classes. These classes call
+/// @ref HttpResponseCreatorFactory::create to retrieve an instance of the
+/// appropriate @ref HttpResponseCreator, which is in turn used to generate
+/// HTTP response.
+///
+/// Note that an implementation of the @ref HttpResponseCreatorFactory::create
+/// may always return the same instance of the @ref HttpResponseCreator
+/// if creating new instance for each request is not required or undesired.
+class HttpResponseCreatorFactory {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~HttpResponseCreatorFactory() { }
+
+ /// @brief Returns an instance of the @ref HttpResponseCreator.
+ ///
+ /// The implementation may create new instance every time this method
+ /// is called, or it may always return the same instance.
+ ///
+ /// @return Pointer to the instance of the @ref HttpResponseCreator to
+ /// be used to generate HTTP response.
+ virtual HttpResponseCreatorPtr create() const = 0;
+
+};
+
+/// @brief Pointer to the @ref HttpResponseCreatorFactory.
+typedef boost::shared_ptr<HttpResponseCreatorFactory>
+HttpResponseCreatorFactoryPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/response_json.cc b/src/lib/http/response_json.cc
new file mode 100644
index 0000000000..fc780ae4cd
--- /dev/null
+++ b/src/lib/http/response_json.cc
@@ -0,0 +1,52 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <http/response_json.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace http {
+
+HttpResponseJson::HttpResponseJson(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body)
+ : HttpResponse(version, status_code, CallSetGenericBody::no()) {
+ addHeader("Content-Type", "application/json");
+ // This class provides its own implementation of the setGenericBody.
+ // We call it here unless the derived class calls this constructor
+ // from its own constructor and indicates that we shouldn't set the
+ // generic content in the body.
+ if (generic_body.set_) {
+ setGenericBody(status_code);
+ }
+}
+
+void
+HttpResponseJson::setGenericBody(const HttpStatusCode& status_code) {
+ // Only generate the content for the client or server errors. For
+ // other status codes (status OK in particular) the content should
+ // be created using setBodyAsJson or setBody.
+ if (isClientError(status_code) || isServerError(status_code)) {
+ std::map<std::string, ConstElementPtr> map_elements;
+ map_elements["result"] =
+ ConstElementPtr(new IntElement(statusCodeToNumber(status_code)));
+ map_elements["text"] =
+ ConstElementPtr(new StringElement(statusCodeToString(status_code)));
+ auto body = Element::createMap();
+ body->setValue(map_elements);
+ setBodyAsJson(body);
+ }
+}
+
+void
+HttpResponseJson::setBodyAsJson(const ConstElementPtr& json_body) {
+ setBody(json_body->str());
+}
+
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/response_json.h b/src/lib/http/response_json.h
new file mode 100644
index 0000000000..b4136626da
--- /dev/null
+++ b/src/lib/http/response_json.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_JSON_H
+#define HTTP_RESPONSE_JSON_H
+
+#include <cc/data.h>
+#include <http/response.h>
+
+namespace isc {
+namespace http {
+
+class HttpResponseJson;
+
+/// @brief Pointer to the @ref HttpResponseJson object.
+typedef boost::shared_ptr<HttpResponseJson> HttpResponseJsonPtr;
+
+/// @brief Represents HTTP response with JSON content.
+///
+/// This is a specialization of the @ref HttpResponse class which
+/// includes "Content-Type" equal to "application/json". It also provides
+/// methods to create JSON content within HTTP responses.
+class HttpResponseJson : public HttpResponse {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param version HTTP version.
+ /// @param status_code HTTP status code.
+ /// @param generic_body Indicates if the constructor should call
+ /// @c setGenericBody to create a generic content for the given
+ /// status code. This should be set to "no" when the constructor is
+ /// called by the derived class which provides its own implementation
+ /// of the @c setGenericBody method.
+ explicit HttpResponseJson(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body =
+ CallSetGenericBody::yes());
+
+ /// @brief Generates JSON content from the data structures represented
+ /// as @ref data::ConstElementPtr.
+ ///
+ /// @param json_body A data structure representing JSON content.
+ virtual void setBodyAsJson(const data::ConstElementPtr& json_body);
+
+private:
+
+ /// @brief Sets generic body for the given status code.
+ ///
+ /// This method generates JSON content for the HTTP client and server
+ /// errors. The generated JSON structure is a map containing "result"
+ /// value holding HTTP status code (e.g. 400) and the "text" string
+ /// holding a status code description.
+ ///
+ /// @param status_code Status code for which the body should be
+ /// generated.
+ void setGenericBody(const HttpStatusCode& status_code);
+};
+
+}
+}
+
+#endif
diff --git a/src/lib/http/tests/.gitignore b/src/lib/http/tests/.gitignore
new file mode 100644
index 0000000000..7100f8eda1
--- /dev/null
+++ b/src/lib/http/tests/.gitignore
@@ -0,0 +1 @@
+/libhttp_unittests
diff --git a/src/lib/http/tests/Makefile.am b/src/lib/http/tests/Makefile.am
new file mode 100644
index 0000000000..d2c8ec3d48
--- /dev/null
+++ b/src/lib/http/tests/Makefile.am
@@ -0,0 +1,51 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/http/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += libhttp_unittests
+
+libhttp_unittests_SOURCES = connection_pool_unittests.cc
+libhttp_unittests_SOURCES += date_time_unittests.cc
+libhttp_unittests_SOURCES += listener_unittests.cc
+libhttp_unittests_SOURCES += post_request_json_unittests.cc
+libhttp_unittests_SOURCES += request_parser_unittests.cc
+libhttp_unittests_SOURCES += request_test.h
+libhttp_unittests_SOURCES += response_creator_unittests.cc
+libhttp_unittests_SOURCES += response_test.h
+libhttp_unittests_SOURCES += request_unittests.cc
+libhttp_unittests_SOURCES += response_unittests.cc
+libhttp_unittests_SOURCES += response_json_unittests.cc
+libhttp_unittests_SOURCES += run_unittests.cc
+
+libhttp_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libhttp_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+libhttp_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+libhttp_unittests_LDADD = $(top_builddir)/src/lib/http/libkea-http.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libhttp_unittests_LDADD += $(LOG4CPLUS_LIBS)
+libhttp_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/http/tests/connection_pool_unittests.cc b/src/lib/http/tests/connection_pool_unittests.cc
new file mode 100644
index 0000000000..3cf2797e20
--- /dev/null
+++ b/src/lib/http/tests/connection_pool_unittests.cc
@@ -0,0 +1,192 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <http/http_acceptor.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace isc::asiolink;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const ConstHttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+ // The simplest thing is to create a response with no content.
+ // We don't need content to test our class.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ return (response);
+ }
+};
+
+/// @brief Derivation of @ref HttpConnectionPool exposing protected member.
+class TestHttpConnectionPool : public HttpConnectionPool {
+public:
+
+ using HttpConnectionPool::connections_;
+
+ /// @brief Checks if specified connection belongs to the pool.
+ bool hasConnection(const HttpConnectionPtr& conn) const {
+ return (std::find(connections_.begin(), connections_.end(), conn)
+ != connections_.end());
+ }
+
+};
+
+/// @brief Test fixture class for @ref HttpConnectionPool.
+class HttpConnectionPoolTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ HttpConnectionPoolTest()
+ : io_service_(), acceptor_(io_service_), connection_pool_(),
+ response_creator_(new TestHttpResponseCreator()) {
+ }
+
+ IOService io_service_; ///< IO service.
+ HttpAcceptor acceptor_; ///< Test acceptor.
+ HttpConnectionPool connection_pool_; ///< Test connection pool.
+ HttpResponseCreatorPtr response_creator_; ///< Test response creator.
+
+};
+
+// This test verifies that connections can be added to the pool and removed.
+TEST_F(HttpConnectionPoolTest, startStop) {
+ // Create two distinct connections.
+ HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ 1000));
+ HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ 1000));
+ // The pool should be initially empty.
+ TestHttpConnectionPool pool;
+ ASSERT_TRUE(pool.connections_.empty());
+
+ // Start first connection and check that it has been added to the pool.
+ ASSERT_NO_THROW(pool.start(conn1));
+ ASSERT_EQ(1, pool.connections_.size());
+ ASSERT_EQ(1, pool.hasConnection(conn1));
+
+ // Start second connection and check that it also has been added.
+ ASSERT_NO_THROW(pool.start(conn2));
+ ASSERT_EQ(2, pool.connections_.size());
+ ASSERT_EQ(1, pool.hasConnection(conn2));
+
+ // Stop first connection.
+ ASSERT_NO_THROW(pool.stop(conn1));
+ ASSERT_EQ(1, pool.connections_.size());
+ // Check that it has been removed but the second connection is still there.
+ ASSERT_EQ(0, pool.hasConnection(conn1));
+ ASSERT_EQ(1, pool.hasConnection(conn2));
+
+ // Remove second connection and verify.
+ ASSERT_NO_THROW(pool.stop(conn2));
+ EXPECT_TRUE(pool.connections_.empty());
+}
+
+// Check that all connections can be remove with a single call.
+TEST_F(HttpConnectionPoolTest, stopAll) {
+ HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ 1000));
+ HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ 1000));
+ TestHttpConnectionPool pool;
+ ASSERT_NO_THROW(pool.start(conn1));
+ ASSERT_NO_THROW(pool.start(conn2));
+
+ // There are two distinct connections in the pool.
+ ASSERT_EQ(2, pool.connections_.size());
+
+ // This should remove all connections.
+ ASSERT_NO_THROW(pool.stopAll());
+ EXPECT_TRUE(pool.connections_.empty());
+}
+
+// Check that stopping non-existing connection is no-op.
+TEST_F(HttpConnectionPoolTest, stopInvalid) {
+ HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ 1000));
+ HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ 1000));
+ TestHttpConnectionPool pool;
+ ASSERT_NO_THROW(pool.start(conn1));
+ ASSERT_NO_THROW(pool.stop(conn2));
+ ASSERT_EQ(1, pool.connections_.size());
+ ASSERT_EQ(1, pool.hasConnection(conn1));
+}
+
+}
diff --git a/src/lib/http/tests/date_time_unittests.cc b/src/lib/http/tests/date_time_unittests.cc
new file mode 100644
index 0000000000..59d75b1311
--- /dev/null
+++ b/src/lib/http/tests/date_time_unittests.cc
@@ -0,0 +1,190 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/date_time.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+using namespace boost::gregorian;
+using namespace boost::posix_time;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @ref HttpDateTime.
+class HttpDateTimeTest : public ::testing::Test {
+public:
+
+ /// @brief Checks time value against expected values.
+ ///
+ /// This method uses value of @ref date_time_ for the test.
+ ///
+ /// @param exp_day_of_week Expected day of week.
+ /// @param exp_day Expected day of month.
+ /// @param exp_month Expected month.
+ /// @param exp_year Expected year.
+ /// @param exp_hours Expected hour value.
+ /// @param exp_minutes Expected minutes value.
+ /// @param exp_seconds Expected seconds value.
+ void testDateTime(const unsigned short exp_day_of_week,
+ const unsigned short exp_day,
+ const unsigned short exp_month,
+ const unsigned short exp_year,
+ const long exp_hours,
+ const long exp_minutes,
+ const long exp_seconds) {
+ // Retrieve @c boost::posix_time::ptime value.
+ ptime as_ptime = date_time_.getPtime();
+ // Date is contained within this object.
+ date date_part = as_ptime.date();
+
+ // Verify weekday.
+ greg_weekday day_of_week = date_part.day_of_week();
+ EXPECT_EQ(exp_day_of_week, day_of_week.as_number());
+
+ // Verify day of month.
+ greg_day day = date_part.day();
+ EXPECT_EQ(exp_day, day.as_number());
+
+ // Verify month.
+ greg_month month = date_part.month();
+ EXPECT_EQ(exp_month, month.as_number());
+
+ // Verify year.
+ greg_year year = date_part.year();
+ EXPECT_EQ(exp_year, static_cast<unsigned short>(year));
+
+ // Retrieve time of the day and verify hour, minute and second.
+ time_duration time_of_day = as_ptime.time_of_day();
+ EXPECT_EQ(exp_hours, time_of_day.hours());
+ EXPECT_EQ(exp_minutes, time_of_day.minutes());
+ EXPECT_EQ(exp_seconds, time_of_day.seconds());
+ }
+
+ /// @brief Date/time value which should be set by the tests.
+ HttpDateTime date_time_;
+
+};
+
+// Test formatting as specified in RFC 1123.
+TEST_F(HttpDateTimeTest, rfc1123Format) {
+ date gdate(greg_year(2002), greg_month(1), greg_day(20));
+ time_duration tm(23, 59, 59, 0);
+ ptime t = ptime(gdate, tm);
+ HttpDateTime date_time(t);
+ std::string formatted;
+ ASSERT_NO_THROW(formatted = date_time.rfc1123Format());
+ EXPECT_EQ("Sun, 20 Jan 2002 23:59:59 GMT", formatted);
+}
+
+// Test formatting as specified in RFC 850.
+TEST_F(HttpDateTimeTest, rfc850Format) {
+ date gdate(greg_year(1994), greg_month(8), greg_day(6));
+ time_duration tm(11, 12, 13, 0);
+ ptime t = ptime(gdate, tm);
+
+ HttpDateTime date_time(t);
+ std::string formatted;
+ ASSERT_NO_THROW(formatted = date_time.rfc850Format());
+ EXPECT_EQ("Saturday, 06-Aug-94 11:12:13 GMT", formatted);
+}
+
+// Test formatting as output of asctime().
+TEST_F(HttpDateTimeTest, asctimeFormat) {
+ date gdate(greg_year(1999), greg_month(11), greg_day(2));
+ time_duration tm(03, 57, 12, 0);
+ ptime t = ptime(gdate, tm);
+
+ HttpDateTime date_time(t);
+ std::string formatted;
+ ASSERT_NO_THROW(formatted = date_time.asctimeFormat());
+ EXPECT_EQ("Tue Nov 2 03:57:12 1999", formatted);
+}
+
+// Test parsing time in RFC 1123 format.
+TEST_F(HttpDateTimeTest, fromRfc1123) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 18:53:45 GMT")
+ );
+ testDateTime(3, 21, 12, 2016, 18, 53, 45);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dex 2016 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 43 Dec 2016 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 16 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 18:53:45"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 1853:45 GMT"),
+ HttpTimeConversionError);
+}
+
+// Test parsing time in RFC 850 format.
+TEST_F(HttpDateTimeTest, fromRfc850) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromRfc850("Wednesday, 21-Dec-16 18:53:45 GMT");
+ );
+ testDateTime(3, 21, 12, 2016, 18, 53, 45);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 55-Dec-16 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dex-16 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dec-2016 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dec-16 1853:45 GMT"),
+ HttpTimeConversionError);
+}
+
+// Test parsing time in asctime() format.
+TEST_F(HttpDateTimeTest, fromRfcAsctime) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAsctime("Wed Dec 21 08:49:37 2016");
+ );
+ testDateTime(3, 21, 12, 2016, 8, 49, 37);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dex 21 08:49:37 2016"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 55 08:49:37 2016"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 21 08:49:37 16"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 21 08:4937 2016"),
+ HttpTimeConversionError);
+}
+
+// Test parsing time in RFC 1123 format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyRfc1123) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAny("Thu, 05 Jan 2017 09:15:06 GMT");
+ );
+ testDateTime(4, 5, 1, 2017, 9, 15, 06);
+}
+
+// Test parsing time in RFC 850 format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyRfc850) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAny("Saturday, 18-Feb-17 01:02:10 GMT");
+ );
+ testDateTime(6, 18, 2, 2017, 1, 2, 10);
+}
+
+// Test parsing time in asctime() format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyAsctime) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAny("Wed Mar 1 15:45:07 2017 GMT");
+ );
+ testDateTime(3, 1, 3, 2017, 15, 45, 7);
+}
+
+// Test that HttpDateTime::fromAny throws exception if unsupported format is
+// used.
+TEST_F(HttpDateTimeTest, fromAnyInvalidFormat) {
+ EXPECT_THROW(HttpDateTime::fromAsctime("20020131T235959"),
+ HttpTimeConversionError);
+}
+
+}
diff --git a/src/lib/http/tests/listener_unittests.cc b/src/lib/http/tests/listener_unittests.cc
new file mode 100644
index 0000000000..3b6aa025ce
--- /dev/null
+++ b/src/lib/http/tests/listener_unittests.cc
@@ -0,0 +1,458 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <http/listener.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <list>
+#include <string>
+
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const ConstHttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+ // The simplest thing is to create a response with no content.
+ // We don't need content to test our class.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ return (response);
+ }
+};
+
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+ /// @brief Creates @ref TestHttpResponseCreator instance.
+ virtual HttpResponseCreatorPtr create() const {
+ HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator());
+ return (response_creator);
+ }
+};
+
+/// @brief Entity which can connect to the HTTP server endpoint.
+class HttpClient : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new socket instance. It doesn't connect. Call
+ /// connect() to connect to the server.
+ ///
+ /// @param io_service IO service to be stopped on error.
+ explicit HttpClient(IOService& io_service)
+ : io_service_(io_service.get_io_service()), socket_(io_service_),
+ buf_(), response_() {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the underlying socket if it is open.
+ ~HttpClient() {
+ close();
+ }
+
+ /// @brief Send HTTP request specified in textual format.
+ ///
+ /// @param request HTTP request in the textual format.
+ void startRequest(const std::string& request) {
+ tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT);
+ socket_.async_connect(endpoint,
+ [this, request](const boost::system::error_code& ec) {
+ if (ec) {
+ // One would expect that async_connect wouldn't return
+ // EINPROGRESS error code, but simply wait for the connection
+ // to get established before the handler is invoked. It turns out,
+ // however, that on some OSes the connect handler may receive this
+ // error code which doesn't necessarily indicate a problem.
+ // Making an attempt to write and read from this socket will
+ // typically succeed. So, we ignore this error.
+ if (ec.value() != boost::asio::error::in_progress) {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ }
+ sendRequest(request);
+ });
+ }
+
+ /// @brief Send HTTP request.
+ ///
+ /// @param request HTTP request in the textual format.
+ void sendRequest(const std::string& request) {
+ sendPartialRequest(request);
+ }
+
+ /// @brief Send part of the HTTP request.
+ ///
+ /// @param request part of the HTTP request to be sent.
+ void sendPartialRequest(std::string request) {
+ socket_.async_send(boost::asio::buffer(request.data(), request.size()),
+ [this, request](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) mutable {
+ if (ec) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again make sure there is no garbage in the
+ // bytes_transferred.
+ bytes_transferred = 0;
+
+ } else {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ }
+
+ // Remove the part of the request which has been sent.
+ if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) {
+ request.erase(0, bytes_transferred);
+ }
+
+ // Continue sending request data if there are still some data to be
+ // sent.
+ if (!request.empty()) {
+ sendPartialRequest(request);
+
+ } else {
+ // Request has been sent. Start receiving response.
+ response_.clear();
+ receivePartialResponse();
+ }
+ });
+ }
+
+ /// @brief Receive response from the server.
+ void receivePartialResponse() {
+ socket_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
+ [this](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ if (ec) {
+ // IO service stopped so simply return.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again, make sure that there is no garbage
+ // in the bytes_transferred.
+ bytes_transferred = 0;
+
+ } else {
+ // Error occurred, bail...
+ ADD_FAILURE() << "error occurred while receiving HTTP"
+ " response from the server: " << ec.message();
+ io_service_.stop();
+ }
+ }
+
+ if (bytes_transferred > 0) {
+ response_.insert(response_.end(), buf_.data(),
+ buf_.data() + bytes_transferred);
+ }
+
+ // Two consecutive new lines end the part of the response we're
+ // expecting.
+ if (response_.find("\r\n\r\n", 0) != std::string::npos) {
+ io_service_.stop();
+
+ } else {
+ receivePartialResponse();
+ }
+
+ });
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+ std::string getResponse() const {
+ return (response_);
+ }
+
+private:
+
+ /// @brief Holds reference to the IO service.
+ boost::asio::io_service& io_service_;
+
+ /// @brief A socket used for the connection.
+ boost::asio::ip::tcp::socket socket_;
+
+ /// @brief Buffer into which response is written.
+ std::array<char, 8192> buf_;
+
+ /// @brief Response in the textual format.
+ std::string response_;
+};
+
+/// @brief Pointer to the HttpClient.
+typedef boost::shared_ptr<HttpClient> HttpClientPtr;
+
+/// @brief Test fixture class for @ref HttpListener.
+class HttpListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts.
+ HttpListenerTest()
+ : io_service_(), factory_(new TestHttpResponseCreatorFactory()),
+ test_timer_(io_service_), clients_() {
+ test_timer_.setup(boost::bind(&HttpListenerTest::timeoutHandler, this),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes active HTTP clients.
+ virtual ~HttpListenerTest() {
+ for (auto client = clients_.begin(); client != clients_.end();
+ ++client) {
+ (*client)->close();
+ }
+ }
+
+ /// @brief Connect to the endpoint.
+ ///
+ /// This method creates HttpClient instance and retains it in the clients_
+ /// list.
+ void startRequest(const std::string& request) {
+ HttpClientPtr client(new HttpClient(io_service_));
+ clients_.push_back(client);
+ clients_.back()->startRequest(request);
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ void timeoutHandler() {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ io_service_.stop();
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Pointer to the response creator factory.
+ HttpResponseCreatorFactoryPtr factory_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief List of client connections.
+ std::list<HttpClientPtr> clients_;
+};
+
+// This test verifies that HTTP connection can be established and used to
+// transmit HTTP request and receive a response.
+TEST_F(HttpListenerTest, listen) {
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ factory_, REQUEST_TIMEOUT);
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(io_service_.run());
+ ASSERT_EQ(1, clients_.size());
+ HttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ("HTTP/1.1 200 OK\r\n"
+ "Content-Length: 0\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n",
+ client->getResponse());
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that the HTTP listener can't be started twice.
+TEST_F(HttpListenerTest, startTwice) {
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ factory_, REQUEST_TIMEOUT);
+ ASSERT_NO_THROW(listener.start());
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that Bad Request status is returned when the request
+// is malformed.
+TEST_F(HttpListenerTest, badRequest) {
+ // Content-Type is wrong. This should result in Bad Request status.
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: foo\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ factory_, REQUEST_TIMEOUT);
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(io_service_.run());
+ ASSERT_EQ(1, clients_.size());
+ HttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ client->getResponse());
+}
+
+// This test verifies that NULL pointer can't be specified for the
+// HttpResponseCreatorFactory.
+TEST_F(HttpListenerTest, invalidFactory) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, HttpResponseCreatorFactoryPtr(),
+ REQUEST_TIMEOUT),
+ HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// Request Timeout.
+TEST_F(HttpListenerTest, invalidRequestTimeout) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, factory_, 0),
+ HttpListenerError);
+}
+
+// This test verifies that listener can't be bound to the port to which
+// other server is bound.
+TEST_F(HttpListenerTest, addressInUse) {
+ tcp::acceptor acceptor(io_service_.get_io_service());
+ // Use other port than SERVER_PORT to make sure that this TCP connection
+ // doesn't affect subsequent tests.
+ tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT + 1);
+ acceptor.open(endpoint.protocol());
+ acceptor.bind(endpoint);
+
+ // Listener should report an error when we try to start it because another
+ // acceptor is bound to that port and address.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT + 1, factory_, REQUEST_TIMEOUT);
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected.
+TEST_F(HttpListenerTest, requestTimeout) {
+ // The part of the request specified here is correct but it is not
+ // a complete request.
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length:";
+
+ // Open the listener with the Request Timeout of 1 sec and post the
+ // partial request.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ factory_, 1000);
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(io_service_.run());
+ ASSERT_EQ(1, clients_.size());
+ HttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+
+ // The server should wait for the missing part of the request for 1 second.
+ // The missing part never arrives so the server should respond with the
+ // HTTP Request Timeout status.
+ EXPECT_EQ("HTTP/1.1 408 Request Timeout\r\n"
+ "Content-Length: 44\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 408, \"text\": \"Request Timeout\" }",
+ client->getResponse());
+}
+
+}
diff --git a/src/lib/http/tests/post_request_json_unittests.cc b/src/lib/http/tests/post_request_json_unittests.cc
new file mode 100644
index 0000000000..f335521765
--- /dev/null
+++ b/src/lib/http/tests/post_request_json_unittests.cc
@@ -0,0 +1,173 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <http/http_types.h>
+#include <http/post_request_json.h>
+#include <http/tests/request_test.h>
+#include <gtest/gtest.h>
+#include <map>
+
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test fixture class for @ref PostHttpRequestJson.
+class PostHttpRequestJsonTest :
+ public HttpRequestTestBase<PostHttpRequestJson> {
+public:
+
+ /// @brief Constructor.
+ PostHttpRequestJsonTest()
+ : HttpRequestTestBase<PostHttpRequestJson>(),
+ json_body_("{ \"service\": \"dhcp4\", \"param1\": \"foo\" }") {
+ }
+
+ /// @brief Sets new JSON body for the HTTP request context.
+ ///
+ /// If the body parameter is empty, it will use the value of
+ /// @ref json_body_ member. Otherwise, it will assign the body
+ /// provided as parameter.
+ ///
+ /// @param body new body value.
+ void setBody(const std::string& body = "") {
+ request_.context()->body_ = body.empty() ? json_body_ : body;
+ }
+
+ /// @brief Default value of the JSON body.
+ std::string json_body_;
+
+};
+
+// This test verifies that PostHttpRequestJson class only accepts
+// POST messages.
+TEST_F(PostHttpRequestJsonTest, requiredPost) {
+ // Use a GET method that is not supported.
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ ASSERT_THROW(request_.create(), HttpRequestError);
+
+ // Now use POST. It should be accepted.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_.create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header equal to "application/json".
+TEST_F(PostHttpRequestJsonTest, requireContentTypeJson) {
+ // Specify "Content-Type" other than "application/json".
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_.create(), HttpRequestError);
+
+ // This time specify correct "Content-Type". It should pass.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_.create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header.
+TEST_F(PostHttpRequestJsonTest, requireContentLength) {
+ // "Content-Length" is not specified initially. It should fail.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_.create(), HttpRequestError);
+
+ // Specify "Content-Length". It should pass.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+}
+
+// This test verifies that JSON body can be retrieved from the
+// HTTP request.
+TEST_F(PostHttpRequestJsonTest, getBodyAsJson) {
+ // Create HTTP POST request with JSON body.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+ setBody();
+
+ ASSERT_NO_THROW(request_.finalize());
+
+ // Try to retrieve pointer to the root element of the JSON body.
+ ConstElementPtr json = request_.getBodyAsJson();
+ ASSERT_TRUE(json);
+
+ // Iterate over JSON values and store them in a simple map.
+ std::map<std::string, std::string> config_values;
+ for (auto config_element = json->mapValue().begin();
+ config_element != json->mapValue().end();
+ ++config_element) {
+ ASSERT_FALSE(config_element->first.empty());
+ ASSERT_TRUE(config_element->second);
+ config_values[config_element->first] = config_element->second->stringValue();
+ }
+
+ // Verify the values.
+ EXPECT_EQ("dhcp4", config_values["service"]);
+ EXPECT_EQ("foo", config_values["param1"]);
+}
+
+// This test verifies that an attempt to parse/retrieve malformed
+// JSON structure will cause an exception.
+TEST_F(PostHttpRequestJsonTest, getBodyAsJsonMalformed) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+ // No colon before 123.
+ setBody("{ \"command\" 123 }" );
+
+ EXPECT_THROW(request_.finalize(), HttpRequestJsonError);
+}
+
+// This test verifies that NULL pointer is returned when trying to
+// retrieve root element of the empty JSON structure.
+TEST_F(PostHttpRequestJsonTest, getEmptyJsonBody) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ ASSERT_NO_THROW(request_.finalize());
+
+ ConstElementPtr json = request_.getBodyAsJson();
+ EXPECT_FALSE(json);
+}
+
+// This test verifies that the specific JSON element can be retrieved.
+TEST_F(PostHttpRequestJsonTest, getJsonElement) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+ setBody();
+
+ ASSERT_NO_THROW(request_.finalize());
+
+ ConstElementPtr element;
+ ASSERT_NO_THROW(element = request_.getJsonElement("service"));
+ ASSERT_TRUE(element);
+ EXPECT_EQ("dhcp4", element->stringValue());
+
+ // An attempt to retrieve non-existing element should return NULL.
+ EXPECT_FALSE(request_.getJsonElement("bar"));
+}
+
+}
diff --git a/src/lib/http/tests/post_request_unittests.cc b/src/lib/http/tests/post_request_unittests.cc
new file mode 100644
index 0000000000..a72d7a3764
--- /dev/null
+++ b/src/lib/http/tests/post_request_unittests.cc
@@ -0,0 +1,72 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/post_request.h>
+#include <http/tests/request_test.h>
+#include <gtest/gtest.h>
+
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test fixture class for @ref PostHttpRequest.
+typedef HttpRequestTestBase<PostHttpRequest> PostHttpRequestTest;
+
+// This test verifies that PostHttpRequest class only accepts POST
+// messages.
+TEST_F(PostHttpRequestTest, requirePost) {
+ // Use a GET method that is not supported.
+ setContextBasics("GET", "/isc/org", std::make_pair(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ ASSERT_THROW(request_.create(), HttpRequestError);
+
+ // Now use POST. It should be accepted.
+ setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_.create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header.
+TEST_F(PostHttpRequestTest, requireContentType) {
+ // No "Content-Type". It should fail.
+ setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+
+ ASSERT_THROW(request_.create(), HttpRequestError);
+
+ // There is "Content-Type". It should pass.
+ setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "text/html");
+
+ EXPECT_NO_THROW(request_.create());
+
+}
+
+// This test verifies that PostHttpRequest requires "Content-Type"
+// header.
+TEST_F(PostHttpRequestTest, requireContentLength) {
+ // No "Content-Length". It should fail.
+ setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_.create(), HttpRequestError);
+
+ // There is "Content-Length". It should pass.
+ setContextBasics("POST", "/isc/org", std::make_pair(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+}
+
+}
diff --git a/src/lib/http/tests/request_parser_unittests.cc b/src/lib/http/tests/request_parser_unittests.cc
new file mode 100644
index 0000000000..8860ff8760
--- /dev/null
+++ b/src/lib/http/tests/request_parser_unittests.cc
@@ -0,0 +1,328 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <http/http_types.h>
+#include <http/request_parser.h>
+#include <http/post_request_json.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @ref HttpRequestParser.
+class HttpRequestParserTest : public ::testing::Test {
+public:
+
+ /// @brief Creates HTTP request string.
+ ///
+ /// @param preamble A string including HTTP request's first line
+ /// and all headers except "Content-Length".
+ /// @param payload A string containing HTTP request payload.
+ std::string createRequestString(const std::string& preamble,
+ const std::string& payload) {
+ std::ostringstream s;
+ s << preamble;
+ s << "Content-Length: " << payload.length() << "\r\n\r\n"
+ << payload;
+ return (s.str());
+ }
+
+ /// @brief Parses the HTTP request and checks that parsing was
+ /// successful.
+ ///
+ /// @param http_req HTTP request string.
+ void doParse(const std::string& http_req) {
+ HttpRequestParser parser(request_);
+ ASSERT_NO_THROW(parser.initModel());
+
+ parser.postBuffer(&http_req[0], http_req.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ ASSERT_FALSE(parser.needData());
+ ASSERT_TRUE(parser.httpParseOk());
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+ }
+
+ /// @brief Tests that parsing fails when malformed HTTP request
+ /// is received.
+ ///
+ /// @param http_req HTTP request string.
+ void testInvalidHttpRequest(const std::string& http_req) {
+ HttpRequestParser parser(request_);
+ ASSERT_NO_THROW(parser.initModel());
+
+ parser.postBuffer(&http_req[0], http_req.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ EXPECT_FALSE(parser.needData());
+ EXPECT_FALSE(parser.httpParseOk());
+ EXPECT_FALSE(parser.getErrorMessage().empty());
+ }
+
+ /// @brief Instance of the HttpRequest used by the unit tests.
+ HttpRequest request_;
+};
+
+// Test test verifies that an HTTP request including JSON body is parsed
+// successfully.
+TEST_F(HttpRequestParserTest, postHttpRequestWithJson) {
+ std::string http_req = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "{ \"service\": \"dhcp4\", \"command\": \"shutdown\" }";
+
+ http_req = createRequestString(http_req, json);
+
+ // Create HTTP request which accepts POST method and JSON as a body.
+ PostHttpRequestJson request;
+
+ // Create a parser and make it use the request we created.
+ HttpRequestParser parser(request);
+ ASSERT_NO_THROW(parser.initModel());
+
+ // Simulate receiving HTTP request in chunks.
+ for (size_t i = 0; i < http_req.size(); i += http_req.size() / 10) {
+ bool done = false;
+ // Get the size of the data chunk.
+ size_t chunk = http_req.size() / 10;
+ // When we're near the end of the data stream, the chunk length may
+ // vary.
+ if (i + chunk > http_req.size()) {
+ chunk = http_req.size() - i;
+ done = true;
+ }
+ // Feed the parser with a data chunk and parse it.
+ parser.postBuffer(&http_req[i], chunk);
+ parser.poll();
+ if (!done) {
+ ASSERT_TRUE(parser.needData());
+ }
+ }
+
+ // Parser should have parsed the request and should expect no more data.
+ ASSERT_FALSE(parser.needData());
+ // Parsing should be successful.
+ ASSERT_TRUE(parser.httpParseOk());
+ // There should be no error message.
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+
+ // Verify parsed headers etc.
+ EXPECT_EQ(HttpRequest::Method::HTTP_POST, request.getMethod());
+ EXPECT_EQ("/foo/bar", request.getUri());
+ EXPECT_EQ("application/json", request.getHeaderValue("Content-Type"));
+ EXPECT_EQ(json.length(), request.getHeaderValueAsUint64("Content-Length"));
+ EXPECT_EQ(1, request.getHttpVersion().major_);
+ EXPECT_EQ(0, request.getHttpVersion().minor_);
+
+ // Try to retrieve values carried in JSON payload.
+ ConstElementPtr json_element;
+ ASSERT_NO_THROW(json_element = request.getJsonElement("service"));
+ EXPECT_EQ("dhcp4", json_element->stringValue());
+
+ ASSERT_NO_THROW(json_element = request.getJsonElement("command"));
+ EXPECT_EQ("shutdown", json_element->stringValue());
+}
+
+// This test verifies that extraneous data in the request will not cause
+// an error if "Content-Length" value refers to the length of the valid
+// part of the request.
+TEST_F(HttpRequestParserTest, extraneousDataInRequest) {
+ std::string http_req = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "{ \"service\": \"dhcp4\", \"command\": \"shutdown\" }";
+
+ // Create valid request;
+ http_req = createRequestString(http_req, json);
+
+ // Add some garbage at the end.
+ http_req += "some stuff which, if parsed, will cause errors";
+
+ // Create HTTP request which accepts POST method and JSON as a body.
+ PostHttpRequestJson request;
+
+ // Create a parser and make it use the request we created.
+ HttpRequestParser parser(request);
+ ASSERT_NO_THROW(parser.initModel());
+
+ // Feed the parser with the request containing some garbage at the end.
+ parser.postBuffer(&http_req[0], http_req.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ // The parser should only parse the valid part of the request as indicated
+ // by the Content-Length.
+ ASSERT_FALSE(parser.needData());
+ ASSERT_TRUE(parser.httpParseOk());
+ // There should be no error message.
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+
+ // Do another poll() to see if the parser will parse the garbage. We
+ // expect that it doesn't.
+ ASSERT_NO_THROW(parser.poll());
+ EXPECT_FALSE(parser.needData());
+ EXPECT_TRUE(parser.httpParseOk());
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+}
+
+
+// This test verifies that LWS is parsed correctly. The LWS marks line breaks
+// in the HTTP header values.
+TEST_F(HttpRequestParserTest, getLWS) {
+ // "User-Agent" header contains line breaks with whitespaces in the new
+ // lines to mark continuation of the header value.
+ std::string http_req = "GET /foo/bar HTTP/1.1\r\n"
+ "Content-Type: text/html\r\n"
+ "User-Agent: Kea/1.2 Command \r\n"
+ " Control \r\n"
+ "\tClient\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ // Verify parsed values.
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ("Kea/1.2 Command Control Client",
+ request_.getHeaderValue("User-Agent"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the HTTP request with no headers is
+// parsed correctly.
+TEST_F(HttpRequestParserTest, noHeaders) {
+ std::string http_req = "GET /foo/bar HTTP/1.1\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ // Verify the values.
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the HTTP method can be specified in lower
+// case.
+TEST_F(HttpRequestParserTest, getLowerCase) {
+ std::string http_req = "get /foo/bar HTTP/1.1\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that other value of the HTTP version can be
+// specified in the request.
+TEST_F(HttpRequestParserTest, http20) {
+ std::string http_req = "get /foo/bar HTTP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(2, request_.getHttpVersion().major_);
+ EXPECT_EQ(0, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the header with no whitespace between the
+// colon and header value is accepted.
+TEST_F(HttpRequestParserTest, noHeaderWhitespace) {
+ std::string http_req = "get /foo/bar HTTP/1.0\r\n"
+ "Content-Type:text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(0, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the header value preceded with multiple
+// whitespaces is accepted.
+TEST_F(HttpRequestParserTest, multipleLeadingHeaderWhitespaces) {
+ std::string http_req = "get /foo/bar HTTP/1.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(0, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that error is reported when unsupported HTTP
+// method is used.
+TEST_F(HttpRequestParserTest, unsupportedMethod) {
+ std::string http_req = "POSTX /foo/bar HTTP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when URI contains
+// an invalid character.
+TEST_F(HttpRequestParserTest, invalidUri) {
+ std::string http_req = "POST /foo/\r HTTP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that the request containing a typo in the
+// HTTP version string causes parsing error.
+TEST_F(HttpRequestParserTest, invalidHTTPString) {
+ std::string http_req = "POST /foo/ HTLP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when the HTTP version
+// string doesn't contain a slash character.
+TEST_F(HttpRequestParserTest, invalidHttpVersionNoSlash) {
+ std::string http_req = "POST /foo/ HTTP 1.1\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP version string
+// doesn't contain the minor version number.
+TEST_F(HttpRequestParserTest, invalidHttpNoMinorVersion) {
+ std::string http_req = "POST /foo/ HTTP/1\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP header name
+// contains an invalid character.
+TEST_F(HttpRequestParserTest, invalidHeaderName) {
+ std::string http_req = "POST /foo/ HTTP/1.1\r\n"
+ "Content-;: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP header value
+// is not preceded with the colon character.
+TEST_F(HttpRequestParserTest, noColonInHttpHeader) {
+ std::string http_req = "POST /foo/ HTTP/1.1\r\n"
+ "Content-Type text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+}
diff --git a/src/lib/http/tests/request_test.h b/src/lib/http/tests/request_test.h
new file mode 100644
index 0000000000..f2755ec8d2
--- /dev/null
+++ b/src/lib/http/tests/request_test.h
@@ -0,0 +1,84 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_REQUEST_TEST_H
+#define HTTP_REQUEST_TEST_H
+
+#include <http/http_types.h>
+#include <http/request.h>
+#include <boost/lexical_cast.hpp>
+#include <gtest/gtest.h>
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace http {
+namespace test {
+
+/// @brief Base test fixture class for testing @ref HttpRequest class and its
+/// derivations.
+///
+/// @tparam HttpRequestType Class under test.
+template<typename HttpRequestType>
+class HttpRequestTestBase : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates HTTP request to be used in unit tests.
+ HttpRequestTestBase()
+ : request_() {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Does nothing.
+ virtual ~HttpRequestTestBase() {
+ }
+
+ /// @brief Initializes HTTP request context with basic information.
+ ///
+ /// It sets:
+ /// - HTTP method,
+ /// - URI,
+ /// - HTTP version number.
+ ///
+ /// @param method HTTP method as string.
+ /// @param uri URI.
+ /// @param version A pair of values of which the first is the major HTTP
+ /// version and the second is the minor HTTP version.
+ void setContextBasics(const std::string& method, const std::string& uri,
+ const HttpVersion& version) {
+ request_.context()->method_ = method;
+ request_.context()->uri_ = uri;
+ request_.context()->http_version_major_ = version.major_;
+ request_.context()->http_version_minor_ = version.minor_;
+ }
+
+ /// @brief Adds HTTP header to the context.
+ ///
+ /// @param header_name HTTP header name.
+ /// @param header_value HTTP header value. This value will be converted to
+ /// a string using @c boost::lexical_cast.
+ /// @tparam ValueType Header value type.
+ template<typename ValueType>
+ void addHeaderToContext(const std::string& header_name,
+ const ValueType& header_value) {
+ request_.context()->headers_.push_back(HttpHeaderContext());
+ request_.context()->headers_.back().name_ = header_name;
+ request_.context()->headers_.back().value_ =
+ boost::lexical_cast<std::string>(header_value);
+ }
+
+ /// @brief Instance of the @ref HttpRequest or its derivation.
+ HttpRequestType request_;
+};
+
+} // namespace test
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/tests/request_unittests.cc b/src/lib/http/tests/request_unittests.cc
new file mode 100644
index 0000000000..ae6bbf20f9
--- /dev/null
+++ b/src/lib/http/tests/request_unittests.cc
@@ -0,0 +1,158 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <http/request.h>
+#include <http/http_types.h>
+#include <http/tests/request_test.h>
+#include <boost/lexical_cast.hpp>
+#include <gtest/gtest.h>
+#include <utility>
+
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+typedef HttpRequestTestBase<HttpRequest> HttpRequestTest;
+
+TEST_F(HttpRequestTest, minimal) {
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+ ASSERT_NO_THROW(request_.create());
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/isc/org", request_.getUri());
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+
+ EXPECT_THROW(request_.getHeaderValue("Content-Length"),
+ HttpRequestNonExistingHeader);
+}
+
+TEST_F(HttpRequestTest, includeHeaders) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", "1024");
+ addHeaderToContext("Content-Type", "application/json");
+ ASSERT_NO_THROW(request_.create());
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_POST, request_.getMethod());
+ EXPECT_EQ("/isc/org", request_.getUri());
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(0, request_.getHttpVersion().minor_);
+
+ std::string content_type;
+ ASSERT_NO_THROW(content_type = request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ("application/json", content_type);
+
+ uint64_t content_length;
+ ASSERT_NO_THROW(
+ content_length = request_.getHeaderValueAsUint64("Content-Length")
+ );
+ EXPECT_EQ(1024, content_length);
+}
+
+TEST_F(HttpRequestTest, requiredMethods) {
+ request_.requireHttpMethod(HttpRequest::Method::HTTP_GET);
+ request_.requireHttpMethod(HttpRequest::Method::HTTP_POST);
+
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+
+ ASSERT_NO_THROW(request_.create());
+
+ request_.context()->method_ = "POST";
+ ASSERT_NO_THROW(request_.create());
+
+ request_.context()->method_ = "PUT";
+ EXPECT_THROW(request_.create(), HttpRequestError);
+}
+
+TEST_F(HttpRequestTest, requiredHttpVersion) {
+ request_.requireHttpVersion(HttpVersion(1, 0));
+ request_.requireHttpVersion(HttpVersion(1, 1));
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ EXPECT_NO_THROW(request_.create());
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 1));
+ EXPECT_NO_THROW(request_.create());
+
+ setContextBasics("POST", "/isc/org", HttpVersion(2, 0));
+ EXPECT_THROW(request_.create(), HttpRequestError);
+}
+
+TEST_F(HttpRequestTest, requiredHeader) {
+ request_.requireHeader("Content-Length");
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+
+ ASSERT_THROW(request_.create(), HttpRequestError);
+
+ addHeaderToContext("Content-Type", "application/json");
+ ASSERT_THROW(request_.create(), HttpRequestError);
+
+ addHeaderToContext("Content-Length", "2048");
+ EXPECT_NO_THROW(request_.create());
+}
+
+TEST_F(HttpRequestTest, requiredHeaderValue) {
+ request_.requireHeaderValue("Content-Type", "application/json");
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_.create(), HttpRequestError);
+
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_.create());
+}
+
+TEST_F(HttpRequestTest, notCreated) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+ addHeaderToContext("Content-Length", "1024");
+
+ EXPECT_THROW(static_cast<void>(request_.getMethod()), HttpRequestError);
+ EXPECT_THROW(static_cast<void>(request_.getHttpVersion()),
+ HttpRequestError);
+ EXPECT_THROW(static_cast<void>(request_.getUri()), HttpRequestError);
+ EXPECT_THROW(static_cast<void>(request_.getHeaderValue("Content-Type")),
+ HttpRequestError);
+ EXPECT_THROW(static_cast<void>(request_.getHeaderValueAsUint64("Content-Length")),
+ HttpRequestError);
+ EXPECT_THROW(static_cast<void>(request_.getBody()), HttpRequestError);
+
+ ASSERT_NO_THROW(request_.finalize());
+
+ EXPECT_NO_THROW(static_cast<void>(request_.getMethod()));
+ EXPECT_NO_THROW(static_cast<void>(request_.getHttpVersion()));
+ EXPECT_NO_THROW(static_cast<void>(request_.getUri()));
+ EXPECT_NO_THROW(static_cast<void>(request_.getHeaderValue("Content-Type")));
+ EXPECT_NO_THROW(
+ static_cast<void>(request_.getHeaderValueAsUint64("Content-Length"))
+ );
+ EXPECT_NO_THROW(static_cast<void>(request_.getBody()));
+}
+
+TEST_F(HttpRequestTest, getBody) {
+ std::string json_body = "{ \"param1\": \"foo\" }";
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body.length());
+
+ request_.context()->body_ = json_body;
+
+ ASSERT_NO_THROW(request_.finalize());
+
+ EXPECT_EQ(json_body, request_.getBody());
+}
+
+TEST_F(HttpRequestTest, requiresBody) {
+ ASSERT_FALSE(request_.requiresBody());
+ request_.requireHeader("Content-Length");
+ EXPECT_TRUE(request_.requiresBody());
+}
+
+}
diff --git a/src/lib/http/tests/response_creator_unittests.cc b/src/lib/http/tests/response_creator_unittests.cc
new file mode 100644
index 0000000000..d6a462c12d
--- /dev/null
+++ b/src/lib/http/tests/response_creator_unittests.cc
@@ -0,0 +1,124 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/http_types.h>
+#include <http/request.h>
+#include <http/response.h>
+#include <http/response_creator.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new HttpRequest()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const ConstHttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(const ConstHttpRequestPtr& request) {
+ // The simplest thing is to create a response with no content.
+ // We don't need content to test our class.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ return (response);
+ }
+};
+
+// This test verifies that Bad Request status is generated when the request
+// hasn't been finalized.
+TEST(HttpResponseCreatorTest, badRequest) {
+ HttpResponsePtr response;
+ // Create a request but do not finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+
+ // Use test specific implementation of Response Creator. It should
+ // generate HTTP error 400.
+ TestHttpResponseCreator creator;
+ ASSERT_NO_THROW(response = creator.createHttpResponse(request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ response->toString());
+}
+
+// This test verifies that response is generated successfully from the
+// finalized/parsed request.
+TEST(HttpResponseCreatorTest, goodRequest) {
+ HttpResponsePtr response;
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ ASSERT_NO_THROW(request->finalize());
+
+ // Use test specific implementation of the Response Creator to generate
+ // a response.
+ TestHttpResponseCreator creator;
+ ASSERT_NO_THROW(response = creator.createHttpResponse(request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 200 OK\r\n"
+ "Content-Length: 0\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n\r\n",
+ response->toString());
+}
+
+}
diff --git a/src/lib/http/tests/response_json_unittests.cc b/src/lib/http/tests/response_json_unittests.cc
new file mode 100644
index 0000000000..9b005e2c79
--- /dev/null
+++ b/src/lib/http/tests/response_json_unittests.cc
@@ -0,0 +1,149 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <cc/data.h>
+#include <http/http_types.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Response type used in tests.
+typedef TestHttpResponseBase<HttpResponseJson> TestHttpResponseJson;
+
+/// @brief Test fixture class for @ref HttpResponseJson.
+class HttpResponseJsonTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes the following class members:
+ /// - json_string_ - which is a pretty formatted JSON content,
+ /// - json_ - A structure of Element objects representing the JSON,
+ /// - json_string_from_json_ - which is a JSON text converted back from
+ /// the json_ data structure. It is the same content as json_string_
+ /// but has different whitespaces.
+ HttpResponseJsonTest()
+ : json_(), json_string_(), json_string_from_json_() {
+ json_string_ =
+ "["
+ " {"
+ " \"pid\": 8080,"
+ " \"status\": 10,"
+ " \"comment\": \"Nice comment from 8080\""
+ " },"
+ " {"
+ " \"pid\": 8081,"
+ " \"status\": 12,"
+ " \"comment\": \"A comment from 8081\""
+ " }"
+ "]";
+
+ json_ = Element::fromJSON(json_string_);
+ json_string_from_json_ = json_->str();
+ }
+
+ /// @brief Test that the response format is correct.
+ ///
+ /// @param status_code HTTP status code for which the response should
+ /// be tested.
+ /// @param status_message HTTP status message.
+ void testGenericResponse(const HttpStatusCode& status_code,
+ const std::string& status_message) {
+ TestHttpResponseJson response(HttpVersion(1, 0), status_code);
+ std::ostringstream status_message_json;
+ // Build the expected content.
+ status_message_json << "{ \"result\": "
+ << static_cast<uint16_t>(status_code)
+ << ", \"text\": "
+ << "\"" << status_message << "\" }";
+ std::ostringstream response_string;
+ response_string <<
+ "HTTP/1.0 " << static_cast<uint16_t>(status_code) << " "
+ << status_message << "\r\n";
+
+ // The content must only be generated for error codes.
+ if (HttpResponse::isClientError(status_code) ||
+ HttpResponse::isServerError(status_code)) {
+ response_string << "Content-Length: " << status_message_json.str().size()
+ << "\r\n";
+ } else {
+ response_string << "Content-Length: 0\r\n";
+ }
+
+ // Content-Type and Date are automatically included.
+ response_string << "Content-Type: application/json\r\n"
+ "Date: " << response.getDateHeaderValue() << "\r\n\r\n";
+
+ if (HttpResponse::isClientError(status_code) ||
+ HttpResponse::isServerError(status_code)) {
+ response_string << status_message_json.str();
+ }
+
+ // Check that the output is as expected.
+ EXPECT_EQ(response_string.str(), response.toString());
+ }
+
+ /// @brief JSON content represented as structure of Element objects.
+ ConstElementPtr json_;
+
+ /// @brief Pretty formatted JSON content.
+ std::string json_string_;
+
+ /// @brief JSON content parsed back from json_ structure.
+ std::string json_string_from_json_;
+
+};
+
+// Test that the response with custom JSON content is generated properly.
+TEST_F(HttpResponseJsonTest, responseWithContent) {
+ TestHttpResponseJson response(HttpVersion(1, 1), HttpStatusCode::OK);
+ ASSERT_NO_THROW(response.setBodyAsJson(json_));
+
+ std::ostringstream response_string;
+ response_string <<
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: " << json_string_from_json_.length() << "\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: " << response.getDateHeaderValue() << "\r\n\r\n"
+ << json_string_from_json_;
+ EXPECT_EQ(response_string.str(), response.toString());
+}
+
+// Test that generic responses are created properly.
+TEST_F(HttpResponseJsonTest, genericResponse) {
+ testGenericResponse(HttpStatusCode::OK, "OK");
+ testGenericResponse(HttpStatusCode::CREATED, "Created");
+ testGenericResponse(HttpStatusCode::ACCEPTED, "Accepted");
+ testGenericResponse(HttpStatusCode::NO_CONTENT, "No Content");
+ testGenericResponse(HttpStatusCode::MULTIPLE_CHOICES,
+ "Multiple Choices");
+ testGenericResponse(HttpStatusCode::MOVED_PERMANENTLY,
+ "Moved Permanently");
+ testGenericResponse(HttpStatusCode::MOVED_TEMPORARILY,
+ "Moved Temporarily");
+ testGenericResponse(HttpStatusCode::NOT_MODIFIED, "Not Modified");
+ testGenericResponse(HttpStatusCode::BAD_REQUEST, "Bad Request");
+ testGenericResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
+ testGenericResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
+ testGenericResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+ testGenericResponse(HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout");
+ testGenericResponse(HttpStatusCode::INTERNAL_SERVER_ERROR,
+ "Internal Server Error");
+ testGenericResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");
+ testGenericResponse(HttpStatusCode::BAD_GATEWAY, "Bad Gateway");
+ testGenericResponse(HttpStatusCode::SERVICE_UNAVAILABLE,
+ "Service Unavailable");
+}
+
+}
diff --git a/src/lib/http/tests/response_test.h b/src/lib/http/tests/response_test.h
new file mode 100644
index 0000000000..8d4e24e1c6
--- /dev/null
+++ b/src/lib/http/tests/response_test.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_RESPONSE_TEST_H
+#define HTTP_RESPONSE_TEST_H
+
+#include <http/http_types.h>
+#include <http/response.h>
+
+namespace isc {
+namespace http {
+namespace test {
+
+template<typename HttpResponseType>
+class TestHttpResponseBase : public HttpResponseType {
+public:
+
+ TestHttpResponseBase(const HttpVersion& version, const HttpStatusCode& status_code)
+ : HttpResponseType(version, status_code) {
+ }
+
+ virtual std::string getDateHeaderValue() const {
+ return ("Tue, 19 Dec 2016 18:53:35 GMT");
+ }
+
+ std::string generateDateHeaderValue() const {
+ return (HttpResponseType::getDateHeaderValue());
+ }
+};
+
+} // namespace test
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/tests/response_unittests.cc b/src/lib/http/tests/response_unittests.cc
new file mode 100644
index 0000000000..a1f98d4819
--- /dev/null
+++ b/src/lib/http/tests/response_unittests.cc
@@ -0,0 +1,167 @@
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <http/date_time.h>
+#include <http/http_types.h>
+#include <http/response.h>
+#include <http/tests/response_test.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace boost::posix_time;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Response type used in tests.
+typedef TestHttpResponseBase<HttpResponse> TestHttpResponse;
+
+/// @brief Test fixture class for @ref HttpResponse.
+class HttpResponseTest : public ::testing::Test {
+public:
+
+ /// @brief Checks if the format of the response is correct.
+ ///
+ /// @param status_code HTTP status code in the response.
+ /// @param status_message HTTP status message in the response.
+ void testResponse(const HttpStatusCode& status_code,
+ const std::string& status_message) {
+ // Create the response. Because we're using derived class
+ // it returns the fixed value of the Date header, which is
+ // very useful in unit tests.
+ TestHttpResponse response(HttpVersion(1, 0), status_code);
+ response.addHeader("Content-Type", "text/html");
+ std::ostringstream response_string;
+ response_string << "HTTP/1.0 " << static_cast<uint16_t>(status_code)
+ << " " << status_message << "\r\n";
+ EXPECT_EQ(response_string.str(), response.toBriefString());
+
+ response_string
+ << "Content-Length: 0\r\n"
+ << "Content-Type: text/html\r\n"
+ << "Date: " << response.getDateHeaderValue() << "\r\n\r\n";
+ EXPECT_EQ(response_string.str(), response.toString());
+ }
+};
+
+// Test the case of HTTP OK message.
+TEST_F(HttpResponseTest, responseOK) {
+ // Include HTML body.
+ const std::string sample_body =
+ "<html>"
+ "<head><title>Kea page title</title></head>"
+ "<body><h1>Some header</h1></body>"
+ "</html>";
+
+ // Create the message and add some headers.
+ TestHttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK);
+ response.addHeader("Content-Type", "text/html");
+ response.addHeader("Host", "kea.example.org");
+ response.setBody(sample_body);
+
+ // Create a string holding expected response. Note that the Date
+ // is a fixed value returned by the customized TestHttpResponse
+ // class.
+ std::ostringstream response_string;
+ response_string <<
+ "HTTP/1.0 200 OK\r\n"
+ "Content-Length: " << sample_body.length() << "\r\n"
+ "Content-Type: text/html\r\n"
+ "Date: " << response.getDateHeaderValue() << "\r\n"
+ "Host: kea.example.org\r\n\r\n" << sample_body;
+
+ EXPECT_EQ(response_string.str(), response.toString());
+}
+
+// Test generic responses for various status codes.
+TEST_F(HttpResponseTest, genericResponse) {
+ testResponse(HttpStatusCode::OK, "OK");
+ testResponse(HttpStatusCode::CREATED, "Created");
+ testResponse(HttpStatusCode::ACCEPTED, "Accepted");
+ testResponse(HttpStatusCode::NO_CONTENT, "No Content");
+ testResponse(HttpStatusCode::MULTIPLE_CHOICES, "Multiple Choices");
+ testResponse(HttpStatusCode::MOVED_PERMANENTLY, "Moved Permanently");
+ testResponse(HttpStatusCode::MOVED_TEMPORARILY, "Moved Temporarily");
+ testResponse(HttpStatusCode::NOT_MODIFIED, "Not Modified");
+ testResponse(HttpStatusCode::BAD_REQUEST, "Bad Request");
+ testResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
+ testResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
+ testResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+ testResponse(HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout");
+ testResponse(HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error");
+ testResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");
+ testResponse(HttpStatusCode::BAD_GATEWAY, "Bad Gateway");
+ testResponse(HttpStatusCode::SERVICE_UNAVAILABLE, "Service Unavailable");
+}
+
+// Test if the class correctly identifies client errors.
+TEST_F(HttpResponseTest, isClientError) {
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::OK));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::CREATED));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::ACCEPTED));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NO_CONTENT));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MULTIPLE_CHOICES));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MOVED_PERMANENTLY));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MOVED_TEMPORARILY));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NOT_MODIFIED));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::BAD_REQUEST));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::UNAUTHORIZED));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::FORBIDDEN));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::NOT_FOUND));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::REQUEST_TIMEOUT));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::INTERNAL_SERVER_ERROR));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NOT_IMPLEMENTED));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::BAD_GATEWAY));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::SERVICE_UNAVAILABLE));
+}
+
+// Test if the class correctly identifies server errors.
+TEST_F(HttpResponseTest, isServerError) {
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::OK));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::CREATED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::ACCEPTED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NO_CONTENT));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MULTIPLE_CHOICES));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MOVED_PERMANENTLY));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MOVED_TEMPORARILY));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NOT_MODIFIED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::BAD_REQUEST));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::UNAUTHORIZED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::FORBIDDEN));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NOT_FOUND));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::REQUEST_TIMEOUT));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::INTERNAL_SERVER_ERROR));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::NOT_IMPLEMENTED));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::BAD_GATEWAY));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::SERVICE_UNAVAILABLE));
+}
+
+// Test that the generated time value, being included in the Date
+// header, is correct.
+TEST_F(HttpResponseTest, getDateHeaderValue) {
+ // Create a response and retrieve the value to be included in the
+ // Date header. This value should hold a current time in the
+ // RFC1123 format.
+ TestHttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK);
+ std::string generated_date = response.generateDateHeaderValue();
+
+ // Use our date/time utilities to parse this value into the ptime.
+ HttpDateTime parsed_time = HttpDateTime::fromRfc1123(generated_date);
+
+ // Now that we have it converted back, we can check how far this
+ // value is from the current time. To be on the safe side, we check
+ // that it is not later than 10 seconds apart, rather than checking
+ // it for equality. In fact, checking it for equality would almost
+ // certainly cause an error. Especially on a virtual machine.
+ time_duration parsed_to_current =
+ microsec_clock::universal_time() - parsed_time.getPtime();
+ EXPECT_LT(parsed_to_current.seconds(), 10);
+}
+
+}
diff --git a/src/lib/http/tests/run_unittests.cc b/src/lib/http/tests/run_unittests.cc
new file mode 100644
index 0000000000..581febb976
--- /dev/null
+++ b/src/lib/http/tests/run_unittests.cc
@@ -0,0 +1,19 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <log/logger_support.h>
+#include <http/http_log.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index ebf531c0ad..f276564f34 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -9,7 +9,7 @@ CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libkea-log.la
libkea_log_la_SOURCES =
libkea_log_la_SOURCES += logimpl_messages.cc logimpl_messages.h
-libkea_log_la_SOURCES += log_dbglevels.h
+libkea_log_la_SOURCES += log_dbglevels.cc log_dbglevels.h
libkea_log_la_SOURCES += log_formatter.h log_formatter.cc
libkea_log_la_SOURCES += logger.cc logger.h
libkea_log_la_SOURCES += logger_impl.cc logger_impl.h
@@ -48,7 +48,7 @@ libkea_log_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
libkea_log_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
libkea_log_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
libkea_log_la_LIBADD += $(LOG4CPLUS_LIBS)
-libkea_log_la_LDFLAGS = -no-undefined -version-info 2:0:0
+libkea_log_la_LDFLAGS = -no-undefined -version-info 3:0:0
# Specify the headers for copying into the installation directory tree. User-
# written libraries only need the definitions for logger.h and dependencies.
diff --git a/src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc b/src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc
index 5118fa482e..d80f288be3 100644
--- a/src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc
+++ b/src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -18,6 +18,14 @@ using namespace isc::log::interprocess;
using isc::util::unittests::parentReadState;
namespace {
+TEST(InterprocessSyncFileTest, BadFile) {
+ InterprocessSyncFile sync("/no-such--dir/or--file");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_FALSE(locker.isLocked());
+ ASSERT_THROW(locker.lock(), InterprocessSyncFileError);
+}
+
TEST(InterprocessSyncFileTest, TestLock) {
InterprocessSyncFile sync("test");
InterprocessSyncLocker locker(sync);
diff --git a/src/lib/log/interprocess/tests/run_unittests.cc b/src/lib/log/interprocess/tests/run_unittests.cc
index 2d7581efa1..e389428338 100644
--- a/src/lib/log/interprocess/tests/run_unittests.cc
+++ b/src/lib/log/interprocess/tests/run_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,7 +8,7 @@
#include <util/unittests/run_all.h>
#include <stdlib.h>
-// This file uses TEST_DATA_TOPBUILDDIR macro, which must point to a writeable
+// This file uses TEST_DATA_TOPBUILDDIR macro, which must point to a writable
// directory. It will be used for creating a logger lockfile.
int
diff --git a/src/lib/log/log_dbglevels.cc b/src/lib/log/log_dbglevels.cc
new file mode 100644
index 0000000000..86b6f21f4a
--- /dev/null
+++ b/src/lib/log/log_dbglevels.cc
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+namespace isc {
+namespace log {
+
+/// This is given a value of 0 as that is the level selected if debugging is
+/// enabled without giving a level.
+extern const int DBGLVL_START_SHUT = 0;
+extern const int DBGLVL_COMMAND = 10;
+extern const int DBGLVL_COMMAND_DATA = 20;
+
+extern const int DBGLVL_TRACE_BASIC = 40;
+extern const int DBGLVL_TRACE_BASIC_DATA = 45;
+extern const int DBGLVL_TRACE_DETAIL = 50;
+extern const int DBGLVL_TRACE_DETAIL_DATA = 55;
+
+}
+}
diff --git a/src/lib/log/log_dbglevels.h b/src/lib/log/log_dbglevels.h
index 5c4707726f..55d837f52e 100644
--- a/src/lib/log/log_dbglevels.h
+++ b/src/lib/log/log_dbglevels.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -38,7 +38,8 @@
/// enabling debugging. Symbols are prefixed DBGLVL so as not to clash with
/// DBG_ symbols in the various modules.
-namespace {
+namespace isc {
+namespace log {
/// Process startup/shutdown debug messages. Note that these are _debug_
/// messages, as other messages related to startup and shutdown may be output
@@ -46,35 +47,33 @@ namespace {
/// up, the "server started" message could be output at a severity of INFO.
/// "Server starting" and messages indicating the stages in startup should be
/// debug messages output at this severity.
-///
-/// This is given a value of 0 as that is the level selected if debugging is
-/// enabled without giving a level.
-const int DBGLVL_START_SHUT = 0;
+extern const int DBGLVL_START_SHUT;
/// This debug level is reserved for logging the exchange of messages/commands
/// between processes, including configuration messages.
-const int DBGLVL_COMMAND = 10;
+extern const int DBGLVL_COMMAND;
/// If the commands have associated data, this level is when they are printed.
/// This includes configuration messages.
-const int DBGLVL_COMMAND_DATA = 20;
+extern const int DBGLVL_COMMAND_DATA;
// The following constants are suggested values for common operations.
// Depending on the exact nature of the code, modules may or may not use these
// levels.
/// Trace basic operations.
-const int DBGLVL_TRACE_BASIC = 40;
+extern const int DBGLVL_TRACE_BASIC;
/// Trace data associated with the basic operations.
-const int DBGLVL_TRACE_BASIC_DATA = 45;
+extern const int DBGLVL_TRACE_BASIC_DATA;
/// Trace detailed operations.
-const int DBGLVL_TRACE_DETAIL = 50;
+extern const int DBGLVL_TRACE_DETAIL;
/// Trace data associated with detailed operations.
-const int DBGLVL_TRACE_DETAIL_DATA = 55;
+extern const int DBGLVL_TRACE_DETAIL_DATA;
-} // Anonymous namespace
+} // log namespace
+} // isc namespace
#endif // LOG_DBGLVLS_H
diff --git a/src/lib/log/log_messages.cc b/src/lib/log/log_messages.cc
index aa0292f9ec..e1f005cd90 100644
--- a/src/lib/log/log_messages.cc
+++ b/src/lib/log/log_messages.cc
@@ -1,4 +1,4 @@
-// File created from log_messages.mes on Thu Jul 7 15:32:06 2011
+// File created from log_messages.mes on Tue Jan 24 2017 20:17
#include <cstddef>
#include <log/message_types.h>
@@ -52,7 +52,7 @@ const char* values[] = {
"LOG_PREFIX_INVALID_ARG", "line %1: $PREFIX directive has an invalid argument ('%2')",
"LOG_READING_LOCAL_FILE", "reading local message file %1",
"LOG_READ_ERROR", "error reading from message file %1: %2",
- "LOG_UNRECOGNIZED_DIRECTIVE", "line %1: unrecogniszd directive '%2'",
+ "LOG_UNRECOGNIZED_DIRECTIVE", "line %1: unrecognized directive '%2'",
"LOG_WRITE_ERROR", "error writing to %1: %2",
NULL
};
diff --git a/src/lib/log/logger_impl.cc b/src/lib/log/logger_impl.cc
index 66c6379e91..edcac1efc2 100644
--- a/src/lib/log/logger_impl.cc
+++ b/src/lib/log/logger_impl.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -82,7 +82,7 @@ LoggerImpl::~LoggerImpl() {
std::string
LoggerImpl::getVersion() {
std::ostringstream ver;
- ver << "log4plus ";
+ ver << "log4cplus ";
ver << log4cplus::versionStr;
return (ver.str());
}
diff --git a/src/lib/log/logger_manager_impl.cc b/src/lib/log/logger_manager_impl.cc
index a5cf1781e3..6e4186f89e 100644
--- a/src/lib/log/logger_manager_impl.cc
+++ b/src/lib/log/logger_manager_impl.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -166,8 +166,11 @@ void
LoggerManagerImpl::createSyslogAppender(log4cplus::Logger& logger,
const OutputOption& opt)
{
+ log4cplus::helpers::Properties properties;
+ properties.setProperty("ident", getRootLoggerName());
+ properties.setProperty("facility", opt.facility);
log4cplus::SharedAppenderPtr syslogapp(
- new log4cplus::SysLogAppender(opt.facility));
+ new log4cplus::SysLogAppender(properties));
setSyslogAppenderLayout(syslogapp);
logger.addAppender(syslogapp);
}
diff --git a/src/lib/log/message_initializer.h b/src/lib/log/message_initializer.h
index 5521802457..491a578ed0 100644
--- a/src/lib/log/message_initializer.h
+++ b/src/lib/log/message_initializer.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -59,7 +59,7 @@ typedef boost::shared_ptr<LoggerDuplicatesList> LoggerDuplicatesListPtr;
/// To avoid static initialization fiasco problems, the containers shared by
/// all instances of this class are dynamically allocated on first use, and
/// held in the smart pointers which are de-allocated only when all instances
-/// of the class are destructred. After the object has been created with the
+/// of the class are destructed. After the object has been created with the
/// constructor, the \c MessageInitializer::loadDictionary static function is
/// called to populate the messages defined in various instances of the
/// \c MessageInitializer class to the global dictionary.
@@ -137,7 +137,7 @@ private:
/// \brief Holds the pointer to the global dictionary.
///
- /// One or more instances of \c MessageInitalizer are created statically,
+ /// One or more instances of \c MessageInitializer are created statically,
/// the \c MessageDictionary being created by the first one to run. As the
/// \c MessageDictionary is also accessed by the \c MessageInitializer
/// destructor, a smart pointer to it is kept. This avoids the possibility
diff --git a/src/lib/log/tests/logger_example.cc b/src/lib/log/tests/logger_example.cc
index 4fbda1fd38..66d5921bbd 100644
--- a/src/lib/log/tests/logger_example.cc
+++ b/src/lib/log/tests/logger_example.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -118,7 +118,7 @@ int main(int argc, char** argv) {
// In the parsing loop that follows, the construction of the logging
// specification is always "one behind". In other words, the parsing of
- // command-line options updates thge current logging specification/output
+ // command-line options updates the current logging specification/output
// options. When the flag indicating a new logger or output specification
// is encountered, the previous one is added to the list.
//
diff --git a/src/lib/log/tests/logger_manager_unittest.cc b/src/lib/log/tests/logger_manager_unittest.cc
index 1ef9f3f8bf..928a038709 100644
--- a/src/lib/log/tests/logger_manager_unittest.cc
+++ b/src/lib/log/tests/logger_manager_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -110,7 +110,7 @@ public:
static std::string createTempFilename() {
string filename = TEMP_DIR + "/kea_logger_manager_test_XXXXXX";
- // Copy into writeable storage for the call to mkstemp
+ // Copy into writable storage for the call to mkstemp
boost::scoped_array<char> tname(new char[filename.size() + 1]);
strcpy(tname.get(), filename.c_str());
diff --git a/src/lib/process/Makefile.am b/src/lib/process/Makefile.am
index 3a85a7a0bd..d6ed1c7d1f 100644
--- a/src/lib/process/Makefile.am
+++ b/src/lib/process/Makefile.am
@@ -47,7 +47,7 @@ nodist_libkea_process_la_SOURCES = process_messages.cc process_messages.h
libkea_process_la_CXXFLAGS = $(AM_CXXFLAGS)
libkea_process_la_CPPFLAGS = $(AM_CPPFLAGS)
libkea_process_la_LDFLAGS = $(AM_LDFLAGS)
-libkea_process_la_LDFLAGS += -no-undefined -version-info 1:0:0
+libkea_process_la_LDFLAGS += -no-undefined -version-info 0:0:0
libkea_process_la_LIBADD =
libkea_process_la_LIBADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
@@ -60,7 +60,7 @@ libkea_process_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
libkea_process_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
libkea_process_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
libkea_process_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
-libkea_process_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+libkea_process_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
# Specify the headers for copying into the installation directory tree.
libkea_process_includedir = $(pkgincludedir)/process
diff --git a/src/lib/process/d_cfg_mgr.cc b/src/lib/process/d_cfg_mgr.cc
index 21475912c1..c68a1397ad 100644
--- a/src/lib/process/d_cfg_mgr.cc
+++ b/src/lib/process/d_cfg_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -122,8 +122,9 @@ DCfgMgrBase::setContext(DCfgContextBasePtr& context) {
}
isc::data::ConstElementPtr
-DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
- LOG_DEBUG(dctl_logger, DBGLVL_COMMAND,
+DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set,
+ bool check_only) {
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_COMMAND,
DCTL_CONFIG_START).arg(config_set->str());
if (!config_set) {
@@ -147,6 +148,11 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
std::string element_id;
try {
+
+ // Make the configuration mutable so we can then insert default values.
+ ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(config_set);
+ setCfgDefaults(mutable_cfg);
+
// Split the configuration into two maps. The first containing only
// top-level scalar parameters (i.e. globals), the second containing
// non-scalar or object elements (maps, lists, etc...). This allows
@@ -156,7 +162,7 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
ElementMap objects_map;
isc::dhcp::ConfigPair config_pair;
- BOOST_FOREACH(config_pair, config_set->mapValue()) {
+ BOOST_FOREACH(config_pair, mutable_cfg->mapValue()) {
std::string element_id = config_pair.first;
isc::data::ConstElementPtr element = config_pair.second;
switch (element->getType()) {
@@ -203,7 +209,7 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
isc_throw(DCfgMgrBaseError,
"Element required by parsing order is missing: "
<< element_id << " ("
- << config_set->getPosition() << ")");
+ << mutable_cfg->getPosition() << ")");
}
}
@@ -240,8 +246,73 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
}
// Everything was fine. Configuration set processed successfully.
- LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
- answer = isc::config::createAnswer(0, "Configuration committed.");
+ if (!check_only) {
+ LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
+ answer = isc::config::createAnswer(0, "Configuration committed.");
+ } else {
+ answer = isc::config::createAnswer(0, "Configuration seems sane.");
+ LOG_INFO(dctl_logger, DCTL_CONFIG_CHECK_COMPLETE)
+ .arg(getConfigSummary(0))
+ .arg(config::answerToText(answer));
+ }
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(ex.what());
+ answer = isc::config::createAnswer(1, ex.what());
+
+ // An error occurred, so make sure that we restore original context.
+ context_ = original_context;
+ return (answer);
+ }
+
+ if (check_only) {
+ // If this is a configuration check only, then don't actually apply
+ // the configuration and reverse to the previous one.
+ context_ = original_context;
+ }
+
+ return (answer);
+}
+
+isc::data::ConstElementPtr
+DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
+ bool check_only,
+ const std::function<void()>& post_config_cb) {
+ if (!config_set) {
+ return (isc::config::createAnswer(1,
+ std::string("Can't parse NULL config")));
+ }
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_COMMAND,
+ DCTL_CONFIG_START).arg(config_set->str());
+
+ // The parsers implement data inheritance by directly accessing
+ // configuration context. For this reason the data parsers must store
+ // the parsed data into context immediately. This may cause data
+ // inconsistency if the parsing operation fails after the context has been
+ // modified. We need to preserve the original context here
+ // so as we can rollback changes when an error occurs.
+ DCfgContextBasePtr original_context = context_;
+ resetContext();
+
+ // Answer will hold the result returned to the caller.
+ ConstElementPtr answer;
+
+ try {
+ // Let's call the actual implementation
+ answer = parse(config_set, check_only);
+
+ // Everything was fine. Configuration set processed successfully.
+ if (!check_only) {
+ if (post_config_cb) {
+ post_config_cb();
+ }
+
+ LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
+ answer = isc::config::createAnswer(0, "Configuration committed.");
+ } else {
+ LOG_INFO(dctl_logger, DCTL_CONFIG_CHECK_COMPLETE)
+ .arg(getConfigSummary(0))
+ .arg(config::answerToText(answer));
+ }
} catch (const std::exception& ex) {
LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(ex.what());
@@ -252,42 +323,44 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
return (answer);
}
+ if (check_only) {
+ // If this is a configuration check only, then don't actually apply
+ // the configuration and reverse to the previous one.
+ context_ = original_context;
+ }
+
return (answer);
}
+
+void
+DCfgMgrBase::setCfgDefaults(isc::data::ElementPtr) {
+}
+
+void
+DCfgMgrBase::parseElement(const std::string&, isc::data::ConstElementPtr) {
+};
+
+
void
DCfgMgrBase::buildParams(isc::data::ConstElementPtr params_config) {
// Loop through scalars parsing them and committing them to storage.
BOOST_FOREACH(dhcp::ConfigPair param, params_config->mapValue()) {
- // Call derivation's method to create the proper parser.
- dhcp::ParserPtr parser(createConfigParser(param.first,
- param.second->getPosition()));
- parser->build(param.second);
- parser->commit();
+ // Call derivation's element parser to parse the element.
+ parseElement(param.first, param.second);
}
}
void DCfgMgrBase::buildAndCommit(std::string& element_id,
isc::data::ConstElementPtr value) {
- // Call derivation's implementation to create the appropriate parser
- // based on the element id.
- ParserPtr parser = createConfigParser(element_id, value->getPosition());
- if (!parser) {
- isc_throw(DCfgMgrBaseError, "Could not create parser");
- }
-
- // Invoke the parser's build method passing in the value. This will
- // "convert" the Element form of value into the actual data item(s)
- // and store them in parser's local storage.
- parser->build(value);
+ // Call derivation's element parser to parse the element.
+ parseElement(element_id, value);
+}
- // Invoke the parser's commit method. This "writes" the data
- // item(s) stored locally by the parser into the context. (Note that
- // parsers are free to do more than update the context, but that is an
- // nothing something we are concerned with here.)
- parser->commit();
+isc::data::ConstElementPtr
+DCfgMgrBase::parse(isc::data::ConstElementPtr, bool) {
+ isc_throw(DCfgMgrBaseError, "This class does not implement simple parser paradigm yet");
}
}; // end of isc::dhcp namespace
}; // end of isc namespace
-
diff --git a/src/lib/process/d_cfg_mgr.h b/src/lib/process/d_cfg_mgr.h
index 087fcea315..1d9e736aff 100644
--- a/src/lib/process/d_cfg_mgr.h
+++ b/src/lib/process/d_cfg_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,8 +8,10 @@
#define D_CFG_MGR_H
#include <cc/data.h>
+#include <cc/cfg_to_element.h>
#include <exceptions/exceptions.h>
#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <functional>
#include <stdint.h>
#include <string>
@@ -57,7 +59,7 @@ typedef boost::shared_ptr<DCfgContextBase> DCfgContextBasePtr;
/// // Restore from backup
/// context_ = backup_copy;
///
-class DCfgContextBase {
+class DCfgContextBase : public isc::data::CfgToElement {
public:
/// @brief Indicator that a configuration parameter is optional.
static const bool OPTIONAL = true;
@@ -180,6 +182,17 @@ public:
/// @return returns a pointer to the new clone.
virtual DCfgContextBasePtr clone() = 0;
+ /// @brief Unparse a configuration object
+ ///
+ /// Returns an element which must parse into the same object, i.e.
+ /// @code
+ /// for all valid config C parse(parse(C)->toElement()) == parse(C)
+ /// @endcode
+ ///
+ /// @return a pointer to a configuration which can be parsed into
+ /// the initial configuration object
+ virtual isc::data::ElementPtr toElement() const = 0;
+
protected:
/// @brief Copy constructor for use by derivations in clone().
DCfgContextBase(const DCfgContextBase& rhs);
@@ -210,6 +223,10 @@ typedef std::vector<std::string> ElementIdList;
/// class which is handed a set of configuration values to process by upper
/// application management layers.
///
+/// This class allows two configuration methods:
+///
+/// 1. classic method
+///
/// The class presents a public method for receiving new configurations,
/// parseConfig. This method coordinates the parsing effort as follows:
///
@@ -230,7 +247,7 @@ typedef std::vector<std::string> ElementIdList;
/// update context with parsed results
/// break on error
///
-/// if an error occurred
+/// if an error occurred or this is only a check
/// restore configuration context from backup
/// @endcode
///
@@ -244,18 +261,32 @@ typedef std::vector<std::string> ElementIdList;
/// This allows a derivation to specify the order in which its elements are
/// parsed if there are dependencies between elements.
///
-/// To parse a given element, its id is passed into createConfigParser,
-/// which returns an instance of the appropriate parser. This method is
-/// abstract so the derivation's implementation determines the type of parser
-/// created. This isolates the knowledge of specific element ids and which
-/// application specific parsers to derivation.
-///
-/// Once the parser has been created, it is used to parse the data value
-/// associated with the element id and update the context with the parsed
-/// results.
+/// To parse a given element, its id along with the element itself,
+/// is passed into the virtual method, @c parseElement. Derivations are
+/// expected to converts the element into application specific object(s),
+/// thereby isolating the CPL from application details.
///
/// In the event that an error occurs, parsing is halted and the
/// configuration context is restored from backup.
+///
+/// See @ref isc::d2::D2CfgMgr and @ref isc::d2::D2Process for example use of
+/// this approach.
+///
+/// 2. simple configuration method
+///
+/// This approach assumes usage of @ref isc::data::SimpleParser paradigm. It
+/// does not use any intermediate storage, does not use parser pointers, does
+/// not enforce parsing order.
+///
+/// Here's the expected control flow order:
+/// 1. implementation calls simpleParseConfig from its configure method.
+/// 2. simpleParseConfig makes a configuration context
+/// 3. parse method from the derived class is called
+/// 4. if the configuration was unsuccessful or this is only a check, the
+/// old context is reinstantiated. If not, the configuration is kept.
+///
+/// See @ref isc::agent::CtrlAgentCfgMgr and @ref isc::agent::CtrlAgentProcess
+/// for example use of this approach.
class DCfgMgrBase {
public:
/// @brief Constructor
@@ -272,13 +303,43 @@ public:
/// @brief Acts as the receiver of new configurations and coordinates
/// the parsing as described in the class brief.
///
- /// @param config_set is a set of configuration elements to parsed.
+ /// @param config_set is a set of configuration elements to be parsed.
+ /// @param check_only true if the config is to be checked only, but not applied
///
/// @return an Element that contains the results of configuration composed
/// of an integer status value (0 means successful, non-zero means failure),
/// and a string explanation of the outcome.
- isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
- config_set);
+ isc::data::ConstElementPtr
+ parseConfig(isc::data::ConstElementPtr config_set,
+ bool check_only = false);
+
+
+ /// @brief Acts as the receiver of new configurations.
+ ///
+ /// This method is similar to what @ref parseConfig does, execept it employs
+ /// the simple parser paradigm: no intermediate storage, no parser pointers
+ /// no distinction between params_map and objects_map, parse order (if needed)
+ /// can be enforced in the actual implementation by calling specific
+ /// parsers first. See @ref isc::agent::CtrlAgentCfgMgr::parse for example.
+ ///
+ /// If check_only is true, the actual parsing is done to check if the configuration
+ /// is sane, but is then reverted.
+ ///
+ /// @param config set of configuration elements to be parsed
+ /// @param check_only true if the config is to be checked only, but not applied
+ /// @param post_config_cb Callback to be executed after the usual parsing stage.
+ /// This can be specified as a C++ lambda which configures other parts of the
+ /// system based on the parsed configuration information. The callback should
+ /// throw an exception to signal an error. This method will catch this
+ /// exception and place an exception string within the result returned.
+ ///
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ isc::data::ConstElementPtr
+ simpleParseConfig(isc::data::ConstElementPtr config,
+ bool check_only = false,
+ const std::function<void()>& post_config_cb = nullptr);
/// @brief Adds a given element id to the end of the parse order list.
///
@@ -320,13 +381,34 @@ public:
virtual std::string getConfigSummary(const uint32_t selection) = 0;
protected:
+ /// @brief Adds default values to the given config
+ ///
+ /// Provides derivations a means to add defaults to a configuration
+ /// Element map prior to parsing it.
+ ///
+ /// @param mutable_config - configuration to which defaults should be added
+ virtual void setCfgDefaults(isc::data::ElementPtr mutable_config);
+
+ /// @brief Parses an individual element
+ ///
+ /// Each element to be parsed is passed into this method to be converted
+ /// into the requisite application object(s).
+ ///
+ /// @param element_id name of the element as it is expected in the cfg
+ /// @param element value of the element as ElementPtr
+ ///
+ virtual void parseElement(const std::string& element_id,
+ isc::data::ConstElementPtr element);
+
/// @brief Parses a set of scalar configuration elements into global
/// parameters
///
/// For each scalar element in the set:
- /// - create a parser for the element
- /// - invoke the parser's build method
- /// - invoke the parser's commit method
+ /// - Invoke parseElement
+ /// - If it returns true go to the next element otherwise:
+ /// - create a parser for the element
+ /// - invoke the parser's build method
+ /// - invoke the parser's commit method
///
/// This will commit the values to context storage making them accessible
/// during object parsing.
@@ -334,24 +416,6 @@ protected:
/// @param params_config set of scalar configuration elements to parse
virtual void buildParams(isc::data::ConstElementPtr params_config);
- /// @brief Create a parser instance based on an element id.
- ///
- /// Given an element_id returns an instance of the appropriate parser.
- /// This method is abstract, isolating any direct knowledge of element_ids
- /// and parsers to within the application-specific derivation.
- ///
- /// @param element_id is the string name of the element as it will appear
- /// in the configuration set.
- /// @param pos position within the configuration text (or file) of element
- /// to be parsed. This is passed for error messaging.
- ///
- /// @return returns a ParserPtr to the parser instance.
- /// @throw throws DCfgMgrBaseError if an error occurs.
- virtual isc::dhcp::ParserPtr
- createConfigParser(const std::string& element_id,
- const isc::data::Element::Position& pos
- = isc::data::Element::Position()) = 0;
-
/// @brief Abstract factory which creates a context instance.
///
/// This method is used at the beginning of configuration process to
@@ -363,7 +427,7 @@ protected:
/// @return Returns a DCfgContextBasePtr to the new context instance.
virtual DCfgContextBasePtr createNewContext() = 0;
- /// @brief Replaces existing context with a new, emtpy context.
+ /// @brief Replaces existing context with a new, empty context.
void resetContext();
/// @brief Update the current context.
@@ -373,12 +437,32 @@ protected:
/// @throw DCfgMgrBaseError if context is NULL.
void setContext(DCfgContextBasePtr& context);
+ /// @brief Parses actual configuration.
+ ///
+ /// This method is expected to be implemented in derived classes that employ
+ /// SimpleParser paradigm (i.e. they call simpleParseConfig rather than
+ /// parseConfig from their configure method).
+ ///
+ /// Implementations that do not employ this method may provide dummy
+ /// implementation.
+ ///
+ /// @param config the Element tree structure that describes the configuration.
+ /// @param check_only false for normal configuration, true when verifying only
+ ///
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr parse(isc::data::ConstElementPtr config,
+ bool check_only);
+
private:
/// @brief Parse a configuration element.
///
- /// Given an element_id and data value, instantiate the appropriate
- /// parser, parse the data value, and commit the results.
+ /// Given an element_id and data value, invoke parseElement. If
+ /// it returns true the return, otherwise created the appropriate
+ /// parser, parse the data value, and commit the results.
+ ///
///
/// @param element_id is the string name of the element as it will appear
/// in the configuration set.
diff --git a/src/lib/process/d_controller.cc b/src/lib/process/d_controller.cc
index 851b97fe15..6f31a83fe8 100644
--- a/src/lib/process/d_controller.cc
+++ b/src/lib/process/d_controller.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,7 +7,6 @@
#include <config.h>
#include <cc/command_interpreter.h>
#include <cfgrpt/config_report.h>
-#include <cryptolink/cryptolink.h>
#include <dhcpsrv/cfgmgr.h>
#include <exceptions/exceptions.h>
#include <log/logger.h>
@@ -29,6 +28,9 @@
#include <sstream>
#include <unistd.h>
+using namespace isc::data;
+using namespace isc::config;
+
namespace isc {
namespace process {
@@ -37,7 +39,7 @@ DControllerBasePtr DControllerBase::controller_;
// Note that the constructor instantiates the controller's primary IOService.
DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
: app_name_(app_name), bin_name_(bin_name),
- verbose_(false), spec_file_name_(""),
+ verbose_(false), check_only_(false), spec_file_name_(""),
io_service_(new isc::asiolink::IOService()),
io_signal_queue_() {
}
@@ -54,6 +56,12 @@ DControllerBase::setController(const DControllerBasePtr& controller) {
controller_ = controller;
}
+ConstElementPtr
+DControllerBase::parseFile(const std::string&) {
+ ConstElementPtr elements;
+ return (elements);
+}
+
void
DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
@@ -62,11 +70,17 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
parseArgs(argc, argv);
} catch (const InvalidUsage& ex) {
usage(ex.what());
- throw; // rethrow it
+ // rethrow it with an empty message
+ isc_throw(InvalidUsage, "");
}
setProcName(bin_name_);
+ if (isCheckOnly()) {
+ checkConfigOnly();
+ return;
+ }
+
// It is important that we set a default logger name because this name
// will be used when the user doesn't provide the logging configuration
// in the Kea configuration file.
@@ -109,12 +123,12 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
"Application Process initialization failed: " << ex.what());
}
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE).arg(app_name_);
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_STANDALONE)
+ .arg(app_name_);
// Step 3 is to load configuration from file.
int rcode;
- isc::data::ConstElementPtr comment
- = isc::config::parseAnswer(rcode, configFromFile());
+ ConstElementPtr comment = parseAnswer(rcode, configFromFile());
if (rcode != 0) {
LOG_FATAL(dctl_logger, DCTL_CONFIG_FILE_LOAD_FAIL)
.arg(app_name_).arg(comment->stringValue());
@@ -141,15 +155,69 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
}
void
+DControllerBase::checkConfigOnly() {
+ try {
+ // We need to initialize logging, in case any error
+ // messages are to be printed.
+ // This is just a test, so we don't care about lockfile.
+ setenv("KEA_LOCKFILE_DIR", "none", 0);
+ isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
+ isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
+ Daemon::loggerInit(bin_name_.c_str(), verbose_);
+
+ // Check the syntax first.
+ std::string config_file = getConfigFile();
+ if (config_file.empty()) {
+ // Basic sanity check: file name must not be empty.
+ isc_throw(InvalidUsage, "JSON configuration file not specified");
+ }
+ ConstElementPtr whole_config = parseFile(config_file);
+ if (!whole_config) {
+ // No fallback to fromJSONFile
+ isc_throw(InvalidUsage, "No configuration found");
+ }
+ if (verbose_) {
+ std::cerr << "Syntax check OK" << std::endl;
+ }
+
+ // Check the logic next.
+ ConstElementPtr module_config;
+ module_config = whole_config->get(getAppName());
+ if (!module_config) {
+ isc_throw(InvalidUsage, "Config file " << config_file <<
+ " does not include '" << getAppName() << "' entry");
+ }
+
+ // Get an application process object.
+ initProcess();
+
+ ConstElementPtr answer = checkConfig(module_config);
+ int rcode = 0;
+ answer = parseAnswer(rcode, answer);
+ if (rcode != 0) {
+ isc_throw(InvalidUsage, "Error encountered: "
+ << answer->stringValue());
+ }
+ } catch (const VersionMessage&) {
+ throw;
+ } catch (const InvalidUsage&) {
+ throw;
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidUsage, "Syntax check failed with: " << ex.what());
+ }
+ return;
+}
+
+void
DControllerBase::parseArgs(int argc, char* argv[])
{
// Iterate over the given command line options. If its a stock option
- // ("s" or "v") handle it here. If its a valid custom option, then
+ // ("c" or "d") handle it here. If its a valid custom option, then
// invoke customOption.
int ch;
opterr = 0;
optind = 1;
- std::string opts("dvVWc:" + getCustomOpts());
+ std::string opts("dvVWc:t:" + getCustomOpts());
while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
switch (ch) {
case 'd':
@@ -168,7 +236,7 @@ DControllerBase::parseArgs(int argc, char* argv[])
// rather than calling exit() here which disrupts gtest.
isc_throw(VersionMessage, getVersion(true));
break;
-
+
case 'W':
// gather Kea config report and throw so main() can catch and
// return rather than calling exit() here which disrupts gtest.
@@ -176,12 +244,17 @@ DControllerBase::parseArgs(int argc, char* argv[])
break;
case 'c':
+ case 't':
// config file name
if (optarg == NULL) {
isc_throw(InvalidUsage, "configuration file name missing");
}
setConfigFile(optarg);
+
+ if (ch == 't') {
+ check_only_ = true;
+ }
break;
case '?': {
@@ -220,7 +293,8 @@ DControllerBase::customOption(int /* option */, char* /*optarg*/)
void
DControllerBase::initProcess() {
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_INIT_PROCESS).arg(app_name_);
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_INIT_PROCESS)
+ .arg(app_name_);
// Invoke virtual method to instantiate the application process.
try {
@@ -240,15 +314,15 @@ DControllerBase::initProcess() {
process_->init();
}
-isc::data::ConstElementPtr
+ConstElementPtr
DControllerBase::configFromFile() {
// Rollback any previous staging configuration. For D2, only a
// logger configuration is used here.
isc::dhcp::CfgMgr::instance().rollback();
// Will hold configuration.
- isc::data::ConstElementPtr module_config;
+ ConstElementPtr module_config;
// Will receive configuration result.
- isc::data::ConstElementPtr answer;
+ ConstElementPtr answer;
try {
std::string config_file = getConfigFile();
if (config_file.empty()) {
@@ -257,9 +331,13 @@ DControllerBase::configFromFile() {
"use -c command line option.");
}
- // Read contents of the file and parse it as JSON
- isc::data::ConstElementPtr whole_config =
- isc::data::Element::fromJSONFile(config_file, true);
+ // If parseFile returns an empty pointer, then pass the file onto the
+ // original JSON parser.
+ ConstElementPtr whole_config = parseFile(config_file);
+ if (!whole_config) {
+ // Read contents of the file and parse it as JSON
+ whole_config = Element::fromJSONFile(config_file, true);
+ }
// Let's configure logging before applying the configuration,
// so we can log things during configuration process.
@@ -282,7 +360,7 @@ DControllerBase::configFromFile() {
answer = updateConfig(module_config);
int rcode = 0;
- isc::config::parseAnswer(rcode, answer);
+ parseAnswer(rcode, answer);
if (!rcode) {
// Configuration successful, so apply the logging configuration
// to log4cplus.
@@ -294,9 +372,8 @@ DControllerBase::configFromFile() {
// Rollback logging configuration.
isc::dhcp::CfgMgr::instance().rollback();
// build an error result
- isc::data::ConstElementPtr error =
- isc::config::createAnswer(1,
- std::string("Configuration parsing failed: ") + ex.what());
+ ConstElementPtr error = createAnswer(COMMAND_ERROR,
+ std::string("Configuration parsing failed: ") + ex.what());
return (error);
}
@@ -306,7 +383,8 @@ DControllerBase::configFromFile() {
void
DControllerBase::runProcess() {
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_RUN_PROCESS).arg(app_name_);
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_RUN_PROCESS)
+ .arg(app_name_);
if (!process_) {
// This should not be possible.
isc_throw(DControllerBaseError, "Process not initialized");
@@ -318,49 +396,168 @@ DControllerBase::runProcess() {
}
// Instance method for handling new config
-isc::data::ConstElementPtr
-DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
- return (process_->configure(new_config));
+ConstElementPtr
+DControllerBase::updateConfig(ConstElementPtr new_config) {
+ return (process_->configure(new_config, false));
}
+// Instance method for checking new config
+ConstElementPtr
+DControllerBase::checkConfig(ConstElementPtr new_config) {
+ return (process_->configure(new_config, true));
+}
-// Instance method for executing commands
-isc::data::ConstElementPtr
-DControllerBase::executeCommand(const std::string& command,
- isc::data::ConstElementPtr args) {
- // Shutdown is universal. If its not that, then try it as
- // a custom command supported by the derivation. If that
- // doesn't pan out either, than send to it the application
- // as it may be supported there.
- isc::data::ConstElementPtr answer;
- if (command.compare(SHUT_DOWN_COMMAND) == 0) {
- answer = shutdownProcess(args);
+ConstElementPtr
+DControllerBase::configGetHandler(const std::string&,
+ ConstElementPtr /*args*/) {
+ ConstElementPtr config = process_->getCfgMgr()->getContext()->toElement();
+
+ return (createAnswer(COMMAND_SUCCESS, config));
+}
+
+ConstElementPtr
+DControllerBase::configWriteHandler(const std::string&,
+ ConstElementPtr args) {
+ std::string filename;
+
+ if (args) {
+ if (args->getType() != Element::map) {
+ return (createAnswer(COMMAND_ERROR, "Argument must be a map"));
+ }
+ ConstElementPtr filename_param = args->get("filename");
+ if (filename_param) {
+ if (filename_param->getType() != Element::string) {
+ return (createAnswer(COMMAND_ERROR,
+ "passed parameter 'filename' "
+ "is not a string"));
+ }
+ filename = filename_param->stringValue();
+ }
+ }
+
+ if (filename.empty()) {
+ // filename parameter was not specified, so let's use
+ // whatever we remember
+ filename = getConfigFile();
+ if (filename.empty()) {
+ return (createAnswer(COMMAND_ERROR,
+ "Unable to determine filename."
+ "Please specify filename explicitly."));
+ }
+ }
+
+
+ // Ok, it's time to write the file.
+ size_t size = 0;
+ ElementPtr cfg = process_->getCfgMgr()->getContext()->toElement();
+
+ // Logging storage is messed up in CA. During its configuration (see
+ // DControllerBase::configFromFile() it calls Daemon::configureLogger()
+ // that stores the logging info in isc::dhcp::CfgMgr::getStagingCfg().
+ // This is later moved to getCurrentCfg() when the configuration is
+ // commited. All control-agent specific configuration is stored in
+ // a structure accessible by process_->getCfgMgr()->getContext(). Note
+ // logging information is not stored there.
+ //
+ // As a result, we need to extract the CA configuration from one
+ // place and logging from another.
+ ConstElementPtr loginfo = isc::dhcp::CfgMgr::instance().getCurrentCfg()->toElement();
+ if (loginfo) {
+ // If there was a config stored in dhcp::CfgMgr, try to get Logging info from it.
+ loginfo = loginfo->get("Logging");
+ }
+ if (loginfo) {
+ // If there is some logging information, add it to our config.
+ cfg->set("Logging", loginfo);
+ }
+
+ try {
+ size = writeConfigFile(filename, cfg);
+ } catch (const isc::Exception& ex) {
+ return (createAnswer(COMMAND_ERROR,
+ std::string("Error during write-config:")
+ + ex.what()));
+ }
+ if (size == 0) {
+ return (createAnswer(COMMAND_ERROR,
+ "Error writing configuration to " + filename));
+ }
+
+ // Ok, it's time to return the successful response.
+ ElementPtr params = Element::createMap();
+ params->set("size", Element::create(static_cast<long long>(size)));
+ params->set("filename", Element::create(filename));
+
+ return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
+ + filename + " successful", params));
+}
+
+ConstElementPtr
+DControllerBase::configTestHandler(const std::string&, ConstElementPtr args) {
+ const int status_code = COMMAND_ERROR; // 1 indicates an error
+ ConstElementPtr module_config;
+ std::string app_name = getAppName();
+ std::string message;
+
+ // Command arguments are expected to be:
+ // { "Module": { ... }, "Logging": { ... } }
+ // The Logging component is technically optional. If it's not supplied
+ // logging will revert to default logging.
+ if (!args) {
+ message = "Missing mandatory 'arguments' parameter.";
} else {
- // It wasn't shutdown, so it may be a custom controller command.
- int rcode = 0;
- answer = customControllerCommand(command, args);
- isc::config::parseAnswer(rcode, answer);
- if (rcode == COMMAND_INVALID)
- {
- // It wasn't a controller command, so it may be an application command.
- answer = process_->command(command, args);
+ module_config = args->get(app_name);
+ if (!module_config) {
+ message = "Missing mandatory '" + app_name + "' parameter.";
+ } else if (module_config->getType() != Element::map) {
+ message = "'" + app_name + "' parameter expected to be a map.";
}
}
+ if (!message.empty()) {
+ // Something is amiss with arguments, return a failure response.
+ ConstElementPtr result = isc::config::createAnswer(status_code,
+ message);
+ return (result);
+ }
+
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ isc::dhcp::CfgMgr::instance().rollback();
+
+ // Now we check the server proper.
+ return (checkConfig(module_config));
+}
+
+ConstElementPtr
+DControllerBase::versionGetHandler(const std::string&, ConstElementPtr) {
+ ConstElementPtr answer;
+
+ // For version-get put the extended version in arguments
+ ElementPtr extended = Element::create(getVersion(true));
+ ElementPtr arguments = Element::createMap();
+ arguments->set("extended", extended);
+ answer = createAnswer(COMMAND_SUCCESS, getVersion(false), arguments);
return (answer);
}
-isc::data::ConstElementPtr
-DControllerBase::customControllerCommand(const std::string& command,
- isc::data::ConstElementPtr /* args */) {
+ConstElementPtr
+DControllerBase::buildReportHandler(const std::string&, ConstElementPtr) {
+ return (createAnswer(COMMAND_SUCCESS, isc::detail::getConfigReport()));
+}
- // Default implementation always returns invalid command.
- return (isc::config::createAnswer(COMMAND_INVALID,
- "Unrecognized command: " + command));
+ConstElementPtr
+DControllerBase::shutdownHandler(const std::string&, ConstElementPtr args) {
+ // Shutdown is universal. If its not that, then try it as
+ // a custom command supported by the derivation. If that
+ // doesn't pan out either, than send to it the application
+ // as it may be supported there.
+ return (shutdownProcess(args));
}
-isc::data::ConstElementPtr
-DControllerBase::shutdownProcess(isc::data::ConstElementPtr args) {
+ConstElementPtr
+DControllerBase::shutdownProcess(ConstElementPtr args) {
if (process_) {
return (process_->shutdown(args));
}
@@ -368,7 +565,7 @@ DControllerBase::shutdownProcess(isc::data::ConstElementPtr args) {
// Not really a failure, but this condition is worth noting. In reality
// it should be pretty hard to cause this.
LOG_WARN(dctl_logger, DCTL_NOT_RUNNING).arg(app_name_);
- return (isc::config::createAnswer(0, "Process has not been initialized."));
+ return (createAnswer(COMMAND_SUCCESS, "Process has not been initialized"));
}
void
@@ -416,9 +613,7 @@ DControllerBase::processSignal(int signum) {
LOG_INFO(dctl_logger, DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD)
.arg(signum).arg(getConfigFile());
int rcode;
- isc::data::ConstElementPtr comment = isc::config::
- parseAnswer(rcode,
- configFromFile());
+ ConstElementPtr comment = parseAnswer(rcode, configFromFile());
if (rcode != 0) {
LOG_ERROR(dctl_logger, DCTL_CFG_FILE_RELOAD_ERROR)
.arg(comment->stringValue());
@@ -430,10 +625,10 @@ DControllerBase::processSignal(int signum) {
case SIGINT:
case SIGTERM:
{
- LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT,
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT,
DCTL_SHUTDOWN_SIGNAL_RECVD).arg(signum);
- isc::data::ElementPtr arg_set;
- executeCommand(SHUT_DOWN_COMMAND, arg_set);
+ ElementPtr arg_set;
+ shutdownHandler(SHUT_DOWN_COMMAND, arg_set);
break;
}
@@ -458,7 +653,9 @@ DControllerBase::usage(const std::string & text)
<< std::endl
<< " -d: optional, verbose output " << std::endl
<< " -c <config file name> : mandatory,"
- << " specifies name of configuration file " << std::endl;
+ << " specify name of configuration file" << std::endl
+ << " -t <config file name> : check the"
+ << " configuration file and exit" << std::endl;
// add any derivation specific usage
std::cerr << getUsageText() << std::endl;
@@ -479,20 +676,7 @@ DControllerBase::getVersion(bool extended) {
tmp << std::endl << EXTENDED_VERSION << std::endl;
tmp << "linked with:" << std::endl;
tmp << isc::log::Logger::getVersion() << std::endl;
- tmp << isc::cryptolink::CryptoLink::getVersion() << std::endl;
- tmp << "database:" << std::endl;
-#ifdef HAVE_MYSQL
- tmp << isc::dhcp::MySqlLeaseMgr::getDBVersion() << std::endl;
-#endif
-#ifdef HAVE_PGSQL
- tmp << isc::dhcp::PgSqlLeaseMgr::getDBVersion() << std::endl;
-#endif
-#ifdef HAVE_CQL
- tmp << isc::dhcp::CqlLeaseMgr::getDBVersion() << std::endl;
-#endif
- tmp << isc::dhcp::Memfile_LeaseMgr::getDBVersion();
-
- // @todo: more details about database runtime
+ tmp << getVersionAddendum();
}
return (tmp.str());
diff --git a/src/lib/process/d_controller.h b/src/lib/process/d_controller.h
index 51d66c71f3..4d781331c5 100644
--- a/src/lib/process/d_controller.h
+++ b/src/lib/process/d_controller.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -19,11 +19,14 @@
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
+#include <string>
+#include <set>
namespace isc {
namespace process {
/// @brief Exception thrown when the command line is invalid.
+/// Can be used to transmit negative messages too.
class InvalidUsage : public isc::Exception {
public:
InvalidUsage(const char* file, size_t line, const char* what) :
@@ -34,7 +37,7 @@ public:
/// Since command line argument parsing is done as part of
/// DControllerBase::launch(), it uses this exception to propagate
/// version information up to main(), when command line argument
-/// -v or -V is given.
+/// -v, -V or -W is given. Can be used to transmit positive messages too.
class VersionMessage : public isc::Exception {
public:
VersionMessage(const char* file, size_t line, const char* what) :
@@ -112,8 +115,8 @@ public:
/// @brief returns Kea version on stdout and exit.
/// redeclaration/redefinition. @ref isc::dhcp::Daemon::getVersion()
- static std::string getVersion(bool extended);
-
+ std::string getVersion(bool extended);
+
/// @brief Acts as the primary entry point into the controller execution
/// and provides the outermost application control logic:
///
@@ -128,7 +131,7 @@ public:
/// arguments.
///
/// This function can be run in "test mode". It prevents initialization
- /// of D2 module logger. This is used in unit tests which initialize logger
+ /// of module logger. This is used in unit tests which initialize logger
/// in their main function. Such a logger uses environmental variables to
/// control severity, verbosity etc.
///
@@ -161,13 +164,32 @@ public:
virtual isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr
new_config);
+ /// @brief Instance method invoked by the configuration event handler and
+ /// which processes the actual configuration check. Provides behavioral
+ /// path for both integrated and stand-alone modes. The current
+ /// implementation will merge the configuration update into the existing
+ /// configuration and then invoke the application process' configure method
+ /// with a final rollback.
+ ///
+ /// @param new_config is the new configuration
+ ///
+ /// @return returns an Element that contains the results of configuration
+ /// update composed of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr checkConfig(isc::data::ConstElementPtr
+ new_config);
+
/// @brief Reconfigures the process from a configuration file
///
/// By default the file is assumed to be a JSON text file whose contents
/// include at least:
///
/// @code
- /// { "<module-name>": {<module-config>} }
+ /// { "<module-name>": {<module-config>}
+ ///
+ /// # Logging element is optional
+ /// ,"Logging": {<logger config}
+ /// }
///
/// where:
/// module-name : is a label which uniquely identifies the
@@ -177,9 +199,17 @@ public:
/// the application's configuration values
/// @endcode
///
- /// The method extracts the set of configuration elements for the
- /// module-name which matches the controller's app_name_ and passes that
- /// set into @c udpateConfig().
+ /// To translate the JSON content into Elements, @c parseFile() is called
+ /// first. This virtual method provides derivations a means to parse the
+ /// file content using an alternate parser. If it returns an empty pointer
+ /// than the JSON parsing providing by Element::fromJSONFile() is called.
+ ///
+ /// Once parsed, the method looks for the Element "Logging" and, if present
+ /// uses it to configure logging.
+ ///
+ /// It then extracts the set of configuration elements for the
+ /// module-name that matches the controller's app_name_ and passes that
+ /// set into @c updateConfig() (or @c checkConfig()).
///
/// The file may contain an arbitrary number of other modules.
///
@@ -188,39 +218,6 @@ public:
/// non-zero means failure), and a string explanation of the outcome.
virtual isc::data::ConstElementPtr configFromFile();
- /// @brief Instance method invoked by the command event handler and which
- /// processes the actual command directive.
- ///
- /// It supports the execution of:
- ///
- /// 1. Stock controller commands - commands common to all DControllerBase
- /// derivations. Currently there is only one, the shutdown command.
- ///
- /// 2. Custom controller commands - commands that the deriving controller
- /// class implements. These commands are executed by the deriving
- /// controller.
- ///
- /// 3. Custom application commands - commands supported by the application
- /// process implementation. These commands are executed by the application
- /// process.
- ///
- /// @param command is a string label representing the command to execute.
- /// @param args is a set of arguments (if any) required for the given
- /// command.
- ///
- /// @return an Element that contains the results of command composed
- /// of an integer status value and a string explanation of the outcome.
- /// The status value is one of the following:
- /// D2::COMMAND_SUCCESS - Command executed successfully
- /// D2::COMMAND_ERROR - Command is valid but suffered an operational
- /// failure.
- /// D2::COMMAND_INVALID - Command is not recognized as valid be either
- /// the controller or the application process.
- virtual isc::data::ConstElementPtr executeCommand(const std::string&
- command,
- isc::data::
- ConstElementPtr args);
-
/// @brief Fetches the name of the application under control.
///
/// @return returns the controller service name string
@@ -235,6 +232,82 @@ public:
return (bin_name_);
}
+ /// @brief handler for version-get command
+ ///
+ /// This method handles the version-get command. It returns the basic and
+ /// extended version.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return answer with version details.
+ isc::data::ConstElementPtr
+ versionGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for 'build-report' command
+ ///
+ /// This method handles build-report command. It returns the output printed
+ /// by configure script which contains most compilation parameters.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return answer with build report
+ isc::data::ConstElementPtr
+ buildReportHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for config-get command
+ ///
+ /// This method handles the config-get command, which retrieves
+ /// the current configuration and returns it in response.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return current configuration wrapped in a response
+ isc::data::ConstElementPtr
+ configGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for config-write command
+ ///
+ /// This handle processes write-config command, which writes the
+ /// current configuration to disk. This command takes one optional
+ /// parameter called filename. If specified, the current configuration
+ /// will be written to that file. If not specified, the file used during
+ /// Kea start-up will be used. To avoid any exploits, the path is
+ /// always relative and .. is not allowed in the filename. This is
+ /// a security measure against exploiting file writes remotely.
+ ///
+ /// @param command (ignored)
+ /// @param args may contain optional string argument filename
+ /// @return status of the configuration file write
+ isc::data::ConstElementPtr
+ configWriteHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for config-test command
+ ///
+ /// This method handles the config-test command, which checks
+ /// configuration specified in args parameter.
+ ///
+ /// @param command (ignored)
+ /// @param args configuration to be checked.
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ configTestHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for 'shutdown' command
+ ///
+ /// This method handles shutdown command. It initiates the shutdown procedure
+ /// using CPL methods.
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return answer confirming that the shutdown procedure is started
+ isc::data::ConstElementPtr
+ shutdownHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
protected:
/// @brief Virtual method that provides derivations the opportunity to
/// support additional command line options. It is invoked during command
@@ -259,26 +332,6 @@ protected:
/// Note this value is subsequently wrapped in a smart pointer.
virtual DProcessBase* createProcess() = 0;
- /// @brief Virtual method that provides derivations the opportunity to
- /// support custom external commands executed by the controller. This
- /// method is invoked by the processCommand if the received command is
- /// not a stock controller command.
- ///
- /// @param command is a string label representing the command to execute.
- /// @param args is a set of arguments (if any) required for the given
- /// command.
- ///
- /// @return an Element that contains the results of command composed
- /// of an integer status value and a string explanation of the outcome.
- /// The status value is one of the following:
- /// D2::COMMAND_SUCCESS - Command executed successfully
- /// D2::COMMAND_ERROR - Command is valid but suffered an operational
- /// failure.
- /// D2::COMMAND_INVALID - Command is not recognized as a valid custom
- /// controller command.
- virtual isc::data::ConstElementPtr customControllerCommand(
- const std::string& command, isc::data::ConstElementPtr args);
-
/// @brief Virtual method which can be used to contribute derivation
/// specific usage text. It is invoked by the usage() method under
/// invalid usage conditions.
@@ -290,7 +343,7 @@ protected:
/// @brief Virtual method which returns a string containing the option
/// letters for any custom command line options supported by the derivation.
- /// These are added to the stock options of "c" and "v" during command
+ /// These are added to the stock options of "c", "d", ..., during command
/// line interpretation.
///
/// @return returns a string containing the custom option letters.
@@ -298,6 +351,13 @@ protected:
return ("");
}
+ /// @brief Check the configuration
+ ///
+ /// Called by @c launch() when @c check_only_ mode is enabled
+ /// @throw VersionMessage when successful but a message should be displayed
+ /// @throw InvalidUsage when an error was detected
+ void checkConfigOnly();
+
/// @brief Application-level signal processing method.
///
/// This method is the last step in processing a OS signal occurrence. It
@@ -330,6 +390,22 @@ protected:
verbose_ = value;
}
+ /// @brief Supplies whether or not check only mode is enabled.
+ ///
+ /// @return returns true if check only is enabled.
+ bool isCheckOnly() const {
+ return (check_only_);
+ }
+
+ /// @brief Method for enabling or disabling check only mode.
+ ///
+ /// @todo this method and @c setVerbose are currently not used.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setCheckOnly(bool value) {
+ check_only_ = value;
+ }
+
/// @brief Getter for fetching the controller's IOService
///
/// @return returns a pointer reference to the IOService.
@@ -373,16 +449,56 @@ protected:
/// list of options with those returned by getCustomOpts(), and uses
/// cstdlib's getopt to loop through the command line.
/// It handles stock options directly, and passes any custom options into
- /// the customOption method. Currently there are only two stock options
- /// -c for specifying the configuration file, and -v for verbose logging.
+ /// the customOption method. Currently there are only some stock options
+ /// -c/t for specifying the configuration file, -d for verbose logging,
+ /// and -v/V/W for version reports.
///
/// @param argc is the number of command line arguments supplied
/// @param argv is the array of string (char *) command line arguments
///
/// @throw InvalidUsage when there are usage errors.
- /// @throw VersionMessage if the -v or -V arguments is given.
+ /// @throw VersionMessage if the -v, -V or -W arguments is given.
void parseArgs(int argc, char* argv[]);
+
+ ///@brief Parse a given file into Elements
+ ///
+ /// This method provides a means for deriving classes to use alternate
+ /// parsing mechanisms to parse configuration files into the corresponding
+ /// isc::data::Elements. The elements produced must be equivalent to those
+ /// which would be produced by the original JSON parsing. Implementations
+ /// should throw when encountering errors.
+ ///
+ /// The default implementation returns an empty pointer, signifying to
+ /// callers that they should submit the file to the original parser.
+ ///
+ /// @param file_name pathname of the file to parse
+ ///
+ /// @return pointer to the elements created
+ ///
+ virtual isc::data::ConstElementPtr parseFile(const std::string& file_name);
+
+ ///@brief Parse text into Elements
+ ///
+ /// This method provides a means for deriving classes to use alternate
+ /// parsing mechanisms to parse configuration text into the corresponding
+ /// isc::data::Elements. The elements produced must be equivalent to those
+ /// which would be produced by the original JSON parsing. Implementations
+ /// should throw when encountering errors.
+ ///
+ /// The default implementation returns an empty pointer, signifying to
+ /// callers that they should submit the text to the original parser.
+ ///
+ /// @param input text to parse
+ ///
+ /// @return pointer to the elements created
+ ///
+ virtual isc::data::ConstElementPtr parseText(const std::string& input) {
+ static_cast<void>(input); // just tu shut up the unused parameter warning
+ isc::data::ConstElementPtr elements;
+ return (elements);
+ }
+
/// @brief Instantiates the application process and then initializes it.
/// This is the second step taken during launch, following successful
/// command line parsing. It is used to invoke the derivation-specific
@@ -471,6 +587,15 @@ protected:
/// This is intended to be used for specific usage violation messages.
void usage(const std::string& text);
+ /// @brief Fetches text containing additional version specifics
+ ///
+ /// This method is provided so derivations can append any additional
+ /// desired information such as library dependencies to the extended
+ /// version text returned when DControllerBase::getVersion(true) is
+ /// invoked.
+ /// @return a string containing additional version info
+ virtual std::string getVersionAddendum() { return (""); }
+
private:
/// @brief Name of the service under control.
/// This name is used as the configuration module name and appears in log
@@ -485,6 +610,10 @@ private:
/// @brief Indicates if the verbose logging mode is enabled.
bool verbose_;
+ /// @brief Indicates if the check only mode for the configuration
+ /// is enabled (usually specified by the command line -t argument).
+ bool check_only_;
+
/// @brief The absolute file name of the JSON spec file.
std::string spec_file_name_;
diff --git a/src/lib/process/d_process.h b/src/lib/process/d_process.h
index aae2612de9..d7c58d9f02 100644
--- a/src/lib/process/d_process.h
+++ b/src/lib/process/d_process.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -25,6 +25,21 @@ public:
isc::Exception(file, line, what) { };
};
+/// @brief String value for the version-get command.
+static const std::string VERSION_GET_COMMAND("version-get");
+
+/// @brief String value for the build-report command.
+static const std::string BUILD_REPORT_COMMAND("build-report");
+
+/// @brief String value for the config-get command.
+static const std::string CONFIG_GET_COMMAND("config-get");
+
+/// @brief String value for the config-write command.
+static const std::string CONFIG_WRITE_COMMAND("config-write");
+
+/// @brief String value for the config-test command.
+static const std::string CONFIG_TEST_COMMAND("config-test");
+
/// @brief String value for the shutdown command.
static const std::string SHUT_DOWN_COMMAND("shutdown");
@@ -102,7 +117,7 @@ public:
///
/// @throw DProcessBaseError if an operational error is encountered.
virtual isc::data::ConstElementPtr
- shutdown(isc::data::ConstElementPtr args) = 0;
+ shutdown(isc::data::ConstElementPtr args) = 0;
/// @brief Processes the given configuration.
///
@@ -113,31 +128,13 @@ public:
/// below.
///
/// @param config_set a new configuration (JSON) for the process
+ /// @param check_only true if configuration is to be verified only, not applied
/// @return an Element that contains the results of configuration composed
/// of an integer status value (0 means successful, non-zero means failure),
/// and a string explanation of the outcome.
- virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
- config_set) = 0;
-
- /// @brief Processes the given command.
- ///
- /// This method is called to execute any custom commands supported by the
- /// process. This method must not throw, it should catch any processing
- /// errors and return a success or failure answer as described below.
- ///
- /// @param command is a string label representing the command to execute.
- /// @param args is a set of arguments (if any) required for the given
- /// command.
- /// @return an Element that contains the results of command composed
- /// of an integer status value:
- ///
- /// - COMMAND_SUCCESS indicates a command was successful.
- /// - COMMAND_ERROR indicates a valid command failed execute.
- /// - COMMAND_INVALID indicates a command is not valid.
- ///
- /// and a string explanation of the outcome.
- virtual isc::data::ConstElementPtr command(
- const std::string& command, isc::data::ConstElementPtr args) = 0;
+ virtual isc::data::ConstElementPtr
+ configure(isc::data::ConstElementPtr config_set,
+ bool check_only = false) = 0;
/// @brief Destructor
virtual ~DProcessBase(){};
diff --git a/src/lib/process/io_service_signal.cc b/src/lib/process/io_service_signal.cc
index 29e3b0cb0e..e8416e4dbe 100644
--- a/src/lib/process/io_service_signal.cc
+++ b/src/lib/process/io_service_signal.cc
@@ -60,7 +60,7 @@ IOSignal::TimerCallback::operator()() {
IOSignalQueue::IOSignalQueue(asiolink::IOServicePtr& io_service)
: io_service_(io_service), signals_() {
if (!io_service_) {
- isc_throw(IOSignalError, "IOSignalQueue - io_serivce cannot be NULL");
+ isc_throw(IOSignalError, "IOSignalQueue - io_service cannot be NULL");
}
}
diff --git a/src/lib/process/io_service_signal.h b/src/lib/process/io_service_signal.h
index 7f088d6027..a393c164a5 100644
--- a/src/lib/process/io_service_signal.h
+++ b/src/lib/process/io_service_signal.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -68,7 +68,7 @@ typedef boost::function<void(IOSignalId sequence_id)> IOSignalHandler;
/// pointer to instigating IOSignal from which the value of OS signal (i.e.
/// SIGINT, SIGUSR1...) can be obtained. Note that calling popSignal()
/// removes the IOSignalPtr from the queue, which should reduce its
-/// reference count to zero upon exiting the handler (unless a delibrate
+/// reference count to zero upon exiting the handler (unless a deliberate
/// copy of it is made).
///
/// A typical IOSignalHandler might be structured as follows:
@@ -190,7 +190,7 @@ typedef std::map<IOSignalId, IOSignalPtr> IOSignalMap;
/// This class is designed specifically to make managing them painless.
/// It maintains an internal map of IOSignals keyed by sequence_id. When a
/// signal is created via the pushSignal() method it is added to the map. When
-/// a signal is retrevied via the popSignal() method it is removed from the map.
+/// a signal is retrieved via the popSignal() method it is removed from the map.
class IOSignalQueue {
public:
/// @brief Constructor
@@ -211,7 +211,7 @@ public:
/// control to IOService::run()).
///
/// @param signum OS signal value of the signal to propagate
- /// @param handler IOSignalHandler to invoke when the signal is delivererd.
+ /// @param handler IOSignalHandler to invoke when the signal is delivered.
///
/// @return The sequence_id of the newly created signal.
///
@@ -231,7 +231,7 @@ public:
///
/// @throw IOSignalError if there is no matching IOSignal in the map for
/// the given sequence_id. Other than by doubling popping, this should be
- /// very unlikley.
+ /// very unlikely.
IOSignalPtr popSignal(IOSignalId sequence_id);
/// @brief Erases the contents of the queue.
diff --git a/src/lib/process/libprocess.dox b/src/lib/process/libprocess.dox
index 66efcf9cfa..c2167df2fc 100644
--- a/src/lib/process/libprocess.dox
+++ b/src/lib/process/libprocess.dox
@@ -103,7 +103,7 @@ isc::asiolink::IOService::run() or its variants. These calls are not
interrupted upon signal receipt as is the select() function and while
boost::asio provides a signal mechanism it requires linking in additional
libraries. Therefore, CPL provides its own signal handling mechanism to
-propagate an OS signal such as SIGHUP to an IOSerivce as a ready event with a
+propagate an OS signal such as SIGHUP to an IOService as a ready event with a
callback.
isc::process::DControllerBase uses two mechanisms to carry out signal handling. It
diff --git a/src/lib/process/process_messages.mes b/src/lib/process/process_messages.mes
index dbb269778c..637d1f5dcd 100644
--- a/src/lib/process/process_messages.mes
+++ b/src/lib/process/process_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -24,10 +24,25 @@ to disconnect from its session with the Kea control channel.
This debug message is issued just before the controller attempts
to establish a session with the Kea control channel.
+% DCTL_CFG_FILE_RELOAD_ERROR configuration reload failed: %1, reverting to current configuration.
+This is an error message indicating that the application attempted to reload
+its configuration from file and encountered an error. This is likely due to
+invalid content in the configuration file. The application should continue
+to operate under its current configuration.
+
+% DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD OS signal %1 received, reloading configuration from file: %2
+This is an informational message indicating the application has received a signal
+instructing it to reload its configuration from file.
+
% DCTL_COMMAND_RECEIVED %1 received command: %2, arguments: %3
A debug message listing the command (and possible arguments) received
from the Kea control system by the controller.
+% DCTL_CONFIG_CHECK_COMPLETE server has completed configuration check: %1, result: %2
+This is an informational message announcing the successful processing of a
+new configuration check is complete. The result of that check is printed.
+This informational message is printed when configuration check is requested.
+
% DCTL_CONFIG_COMPLETE server has completed configuration: %1
This is an informational message announcing the successful processing of a
new configuration. It is output during server startup, and when an updated
@@ -88,16 +103,6 @@ The controller has encountered a fatal error while running the
application and is terminating. The reason for the failure is
included in the message.
-% DCTL_CFG_FILE_RELOAD_ERROR configuration reload failed: %1, reverting to current configuration.
-This is an error message indicating that the application attempted to reload
-its configuration from file and encountered an error. This is likely due to
-invalid content in the configuration file. The application should continue
-to operate under its current configuration.
-
-% DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD OS signal %1 received, reloading configuration from file: %2
-This is an informational message indicating the application has received a signal
-instructing it to reload its configuration from file.
-
% DCTL_RUN_PROCESS %1 starting application event loop
This debug message is issued just before the controller invokes
the application run method.
diff --git a/src/lib/process/tests/d_cfg_mgr_unittests.cc b/src/lib/process/tests/d_cfg_mgr_unittests.cc
index fe37fe5e6c..44bdf92af8 100644
--- a/src/lib/process/tests/d_cfg_mgr_unittests.cc
+++ b/src/lib/process/tests/d_cfg_mgr_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
#include <cc/command_interpreter.h>
#include <config/module_spec.h>
+#include <exceptions/exceptions.h>
#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <process/testutils/d_test_stubs.h>
#include <process/d_cfg_mgr.h>
@@ -22,6 +23,7 @@ using namespace std;
using namespace isc;
using namespace isc::config;
using namespace isc::process;
+using namespace isc::data;
using namespace boost::posix_time;
namespace {
@@ -44,13 +46,6 @@ public:
return (DCfgContextBasePtr());
}
- /// @brief Dummy implementation as this method is abstract.
- virtual isc::dhcp::ParserPtr
- createConfigParser(const std::string& /* element_id */,
- const isc::data::Element::Position& /* pos */) {
- return (isc::dhcp::ParserPtr());
- }
-
/// @brief Returns summary of configuration in the textual format.
virtual std::string getConfigSummary(const uint32_t) {
return ("");
@@ -60,8 +55,8 @@ public:
/// @brief Test fixture class for testing DCfgMgrBase class.
/// It maintains an member instance of DStubCfgMgr and derives from
/// ConfigParseTest fixture, thus providing methods for converting JSON
-/// strings to configuration element sets, checking parse results, and
-/// accessing the configuration context.
+/// strings to configuration element sets, checking parse results,
+/// accessing the configuration context and trying to unparse.
class DStubCfgMgrTest : public ConfigParseTest {
public:
@@ -121,25 +116,22 @@ TEST_F(DStubCfgMgrTest, basicParseTest) {
ASSERT_TRUE(fromJSON(config));
// Verify that we can parse a simple configuration.
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
EXPECT_TRUE(checkAnswer(0));
- // Verify that an error building the element is caught and returns a
- // failed parse result.
- SimFailure::set(SimFailure::ftElementBuild);
- answer_ = cfg_mgr_->parseConfig(config_set_);
- EXPECT_TRUE(checkAnswer(1));
-
- // Verify that an error committing the element is caught and returns a
- // failed parse result.
- SimFailure::set(SimFailure::ftElementCommit);
- answer_ = cfg_mgr_->parseConfig(config_set_);
- EXPECT_TRUE(checkAnswer(1));
+ // Verify that we can check a simple configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_, true);
+ EXPECT_TRUE(checkAnswer(0));
// Verify that an unknown element error is caught and returns a failed
// parse result.
SimFailure::set(SimFailure::ftElementUnknown);
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that an error is caught too when the config is checked for.
+ SimFailure::set(SimFailure::ftElementUnknown);
+ answer_ = cfg_mgr_->parseConfig(config_set_, true);
EXPECT_TRUE(checkAnswer(1));
}
@@ -199,7 +191,7 @@ TEST_F(DStubCfgMgrTest, parseOrderTest) {
EXPECT_EQ(0, cfg_mgr_->getParseOrder().size());
// Parse the configuration, verify it parses without error.
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
EXPECT_TRUE(checkAnswer(0));
// Verify that the parsed order matches what we expected.
@@ -215,7 +207,7 @@ TEST_F(DStubCfgMgrTest, parseOrderTest) {
EXPECT_EQ(1, cfg_mgr_->getParseOrder().size());
// Verify the configuration fails.
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
EXPECT_TRUE(checkAnswer(1));
// Verify that the configuration parses correctly, when the parse order
@@ -230,7 +222,7 @@ TEST_F(DStubCfgMgrTest, parseOrderTest) {
cfg_mgr_->parsed_order_.clear();
// Verify the configuration parses without error.
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
EXPECT_TRUE(checkAnswer(0));
// Build expected order
@@ -256,7 +248,7 @@ TEST_F(DStubCfgMgrTest, parseOrderTest) {
EXPECT_EQ(4, cfg_mgr_->getParseOrder().size());
// Verify the configuration fails.
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
EXPECT_TRUE(checkAnswer(1));
}
@@ -280,7 +272,7 @@ TEST_F(DStubCfgMgrTest, simpleTypesTest) {
ASSERT_TRUE(fromJSON(config));
// Verify that the configuration parses without error.
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
ASSERT_TRUE(checkAnswer(0));
DStubContextPtr context = getStubContext();
ASSERT_TRUE(context);
@@ -319,7 +311,7 @@ TEST_F(DStubCfgMgrTest, simpleTypesTest) {
ASSERT_TRUE(fromJSON(config2));
// Verify that the configuration parses without error.
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
EXPECT_TRUE(checkAnswer(0));
context = getStubContext();
ASSERT_TRUE(context);
@@ -370,7 +362,7 @@ TEST_F(DStubCfgMgrTest, rollBackTest) {
ASSERT_TRUE(fromJSON(config));
// Verify that the configuration parses without error.
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
EXPECT_TRUE(checkAnswer(0));
DStubContextPtr context = getStubContext();
ASSERT_TRUE(context);
@@ -407,7 +399,7 @@ TEST_F(DStubCfgMgrTest, rollBackTest) {
// Force a failure on the last element
SimFailure::set(SimFailure::ftElementUnknown);
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
EXPECT_TRUE(checkAnswer(1));
context = getStubContext();
ASSERT_TRUE(context);
@@ -432,6 +424,76 @@ TEST_F(DStubCfgMgrTest, rollBackTest) {
EXPECT_TRUE(object);
}
+/// @brief Tests that the configuration context is preserved during
+/// check only parsing.
+TEST_F(DStubCfgMgrTest, checkOnly) {
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"map_test\" : {} , "
+ " \"list_test\": [] }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
+ EXPECT_TRUE(checkAnswer(0));
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Verify that all of parameters have the expected values.
+ bool actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ uint32_t actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ std::string actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ isc::data::ConstElementPtr object;
+ EXPECT_NO_THROW(context->getObjectParam("map_test", object));
+ EXPECT_TRUE(object);
+
+ EXPECT_NO_THROW(context->getObjectParam("list_test", object));
+ EXPECT_TRUE(object);
+
+ // Create a configuration which "updates" all of the parameter values.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"map_test2\" : {} , "
+ " \"list_test2\": [] }";
+ ASSERT_TRUE(fromJSON(config2));
+
+ answer_ = cfg_mgr_->parseConfig(config_set_, true);
+ EXPECT_TRUE(checkAnswer(0));
+ context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Verify that all of parameters have the original values.
+ actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ EXPECT_NO_THROW(context->getObjectParam("map_test", object));
+ EXPECT_TRUE(object);
+
+ EXPECT_NO_THROW(context->getObjectParam("list_test", object));
+ EXPECT_TRUE(object);
+}
+
// Tests that configuration element position is returned by getParam variants.
TEST_F(DStubCfgMgrTest, paramPosition) {
// Create a configuration with one of each scalar types. We end them
@@ -442,7 +504,7 @@ TEST_F(DStubCfgMgrTest, paramPosition) {
ASSERT_TRUE(fromJSON(config));
// Verify that the configuration parses without error.
- answer_ = cfg_mgr_->parseConfig(config_set_);
+ answer_ = cfg_mgr_->parseConfig(config_set_, false);
ASSERT_TRUE(checkAnswer(0));
DStubContextPtr context = getStubContext();
ASSERT_TRUE(context);
@@ -477,5 +539,44 @@ TEST_F(DStubCfgMgrTest, paramPosition) {
EXPECT_EQ(pos.file_, isc::data::Element::ZERO_POSITION().file_);
}
+// This tests if some aspects of simpleParseConfig are behaving properly.
+// Thorough testing is only possible for specific implementations. This
+// is done for control agent (see CtrlAgentControllerTest tests in
+// src/bin/agent/tests/ctrl_agent_controller_unittest.cc for example).
+// Also, shell tests in src/bin/agent/ctrl_agent_process_tests.sh test
+// the whole CA process that uses simpleParseConfig. The alternative
+// would be to implement whole parser that would set the context
+// properly. The ROI for this is not worth the effort.
+TEST_F(DStubCfgMgrTest, simpleParseConfig) {
+ using namespace isc::data;
+
+ // Passing just null pointer should result in error return code
+ answer_ = cfg_mgr_->simpleParseConfig(ConstElementPtr(), false);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Ok, now try with a dummy, but valid json code
+ string config = "{ \"bool_test\": true , \n"
+ " \"uint32_test\": 77 , \n"
+ " \"string_test\": \"hmmm chewy\" }";
+ ASSERT_NO_THROW(fromJSON(config));
+
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
+ EXPECT_TRUE(checkAnswer(0));
+}
+
+// This test checks that the post configuration callback function is
+// executed by the simpleParseConfig function.
+TEST_F(DStubCfgMgrTest, simpleParseConfigWithCallback) {
+ string config = "{ \"bool_test\": true , \n"
+ " \"uint32_test\": 77 , \n"
+ " \"string_test\": \"hmmm chewy\" }";
+ ASSERT_NO_THROW(fromJSON(config));
+
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false,
+ [this]() {
+ isc_throw(Unexpected, "unexpected configuration error");
+ });
+ EXPECT_TRUE(checkAnswer(1));
+}
} // end of anonymous namespace
diff --git a/src/lib/process/tests/d_controller_unittests.cc b/src/lib/process/tests/d_controller_unittests.cc
index 1b4d03d1f4..d8b7697772 100644
--- a/src/lib/process/tests/d_controller_unittests.cc
+++ b/src/lib/process/tests/d_controller_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -38,7 +38,7 @@ public:
};
/// @brief Basic Controller instantiation testing.
-/// Verfies that the controller singleton gets created and that the
+/// Verifies that the controller singleton gets created and that the
/// basic derivation from the base class is intact.
TEST_F(DStubControllerTest, basicInstanceTesting) {
// Verify that the singleton exists and it is the correct type.
@@ -188,8 +188,8 @@ TEST_F(DStubControllerTest, launchNormalShutdown) {
elapsed_time.total_milliseconds() <= 2300);
}
-/// @brief Tests launch with an nonexistant configuration file.
-TEST_F(DStubControllerTest, nonexistantConfigFile) {
+/// @brief Tests launch with an non-existing configuration file.
+TEST_F(DStubControllerTest, nonExistingConfigFile) {
// command line to run standalone
char* argv[] = { const_cast<char*>("progName"),
const_cast<char*>("-c"),
@@ -226,7 +226,7 @@ TEST_F(DStubControllerTest, missingConfigFileArgument) {
/// @brief Tests launch with an operational error during application execution.
/// This test creates an interval timer to generate a runtime exception during
-/// the process event loop. It launches wih a valid, stand-alone command line
+/// the process event loop. It launches with a valid, stand-alone command line
/// and no simulated errors. Launch should throw ProcessRunError.
TEST_F(DStubControllerTest, launchRuntimeError) {
// Use an asiolink IntervalTimer and callback to generate the
@@ -272,75 +272,22 @@ TEST_F(DStubControllerTest, configUpdateTests) {
isc::config::parseAnswer(rcode, answer);
EXPECT_EQ(0, rcode);
+ // Verify that a valid config gets a successful check result.
+ answer = checkConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
// Verify that an error in process configure method is handled.
SimFailure::set(SimFailure::ftProcessConfigure);
answer = updateConfig(config_set);
isc::config::parseAnswer(rcode, answer);
EXPECT_EQ(1, rcode);
-}
-
-/// @brief Command execution tests.
-/// This really tests just the ability of the handler to invoke the necessary
-/// chain of methods and to handle error conditions.
-/// This test verifies that:
-/// 1. That an unrecognized command is detected and returns a status of
-/// process::COMMAND_INVALID.
-/// 2. Shutdown command is recognized and returns a process::COMMAND_SUCCESS
-/// status.
-/// 3. A valid, custom controller command is recognized a
-/// process::COMMAND_SUCCESS
-/// status.
-/// 4. A valid, custom process command is recognized a
-/// process::COMMAND_SUCCESS status.
-/// 5. That a valid controller command that fails returns a
-/// process::COMMAND_ERROR.
-/// 6. That a valid process command that fails returns a process::COMMAND_ERROR.
-TEST_F(DStubControllerTest, executeCommandTests) {
- int rcode = -1;
- isc::data::ConstElementPtr answer;
- isc::data::ElementPtr arg_set;
-
- // Initialize the application process.
- ASSERT_NO_THROW(initProcess());
- EXPECT_TRUE(checkProcess());
-
- // Verify that an unknown command returns an process::COMMAND_INVALID
- // response.
- std::string bogus_command("bogus");
- answer = executeCommand(bogus_command, arg_set);
- isc::config::parseAnswer(rcode, answer);
- EXPECT_EQ(COMMAND_INVALID, rcode);
- // Verify that shutdown command returns process::COMMAND_SUCCESS response.
- answer = executeCommand(SHUT_DOWN_COMMAND, arg_set);
- isc::config::parseAnswer(rcode, answer);
- EXPECT_EQ(COMMAND_SUCCESS, rcode);
-
- // Verify that a valid custom controller command returns
- // process::COMMAND_SUCCESS response.
- answer = executeCommand(DStubController::stub_ctl_command_, arg_set);
- isc::config::parseAnswer(rcode, answer);
- EXPECT_EQ(COMMAND_SUCCESS, rcode);
-
- // Verify that a valid custom process command returns
- // process::COMMAND_SUCCESS response.
- answer = executeCommand(DStubProcess::stub_proc_command_, arg_set);
- isc::config::parseAnswer(rcode, answer);
- EXPECT_EQ(COMMAND_SUCCESS, rcode);
-
- // Verify that a valid custom controller command that fails returns
- // a process::COMMAND_ERROR.
- SimFailure::set(SimFailure::ftControllerCommand);
- answer = executeCommand(DStubController::stub_ctl_command_, arg_set);
- isc::config::parseAnswer(rcode, answer);
- EXPECT_EQ(COMMAND_ERROR, rcode);
-
- // Verify that a valid custom process command that fails returns
- // a process::COMMAND_ERROR.
- SimFailure::set(SimFailure::ftProcessCommand);
- answer = executeCommand(DStubProcess::stub_proc_command_, arg_set);
+ // Verify that an error is handled too when the config is checked for.
+ SimFailure::set(SimFailure::ftProcessConfigure);
+ answer = checkConfig(config_set);
isc::config::parseAnswer(rcode, answer);
- EXPECT_EQ(COMMAND_ERROR, rcode);
+ EXPECT_EQ(1, rcode);
}
// Tests that registered signals are caught and handled.
@@ -396,6 +343,35 @@ TEST_F(DStubControllerTest, invalidConfigReload) {
EXPECT_EQ(SIGHUP, signals[0]);
}
+// Tests that the original configuration is retained after a SIGHUP triggered
+// reconfiguration fails due to invalid config content.
+TEST_F(DStubControllerTest, alternateParsing) {
+ controller_->useAlternateParser(true);
+
+ // Setup to raise SIGHUP in 200 ms.
+ TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+ // Write the config and then run launch() for 500 ms
+ // After startup, which will load the initial configuration this enters
+ // the process's runIO() loop. We will first rewrite the config file.
+ // Next we process the SIGHUP signal which should cause us to reconfigure.
+ time_duration elapsed_time;
+ runWithConfig("{ \"string_test\": \"first value\" }", 500, elapsed_time);
+
+ // Context is still available post launch. Check to see that our
+ // configuration value is still the original value.
+ std::string actual_value = "";
+ ASSERT_NO_THROW(getContext()->getParam("string_test", actual_value));
+ EXPECT_EQ("alt value", actual_value);
+
+ // Verify that we saw the signal.
+ std::vector<int>& signals = controller_->getProcessedSignals();
+ ASSERT_EQ(1, signals.size());
+ EXPECT_EQ(SIGHUP, signals[0]);
+}
+
+
+
// Tests that the original configuration is replaced after a SIGHUP triggered
// reconfiguration succeeds.
TEST_F(DStubControllerTest, validConfigReload) {
@@ -442,6 +418,17 @@ TEST_F(DStubControllerTest, sigintShutdown) {
EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
}
+// Verifies that version and extended version information is correct
+TEST_F(DStubControllerTest, getVersion) {
+ std::string text = controller_->getVersion(false);
+ EXPECT_EQ(text,VERSION);
+
+ text = controller_->getVersion(true);
+ EXPECT_NE(std::string::npos, text.find(VERSION));
+ EXPECT_NE(std::string::npos, text.find(EXTENDED_VERSION));
+ EXPECT_NE(std::string::npos, text.find(controller_->getVersionAddendum()));
+}
+
// Tests that the SIGTERM triggers a normal shutdown.
TEST_F(DStubControllerTest, sigtermShutdown) {
// Setup to raise SIGHUP in 1 ms.
diff --git a/src/lib/process/tests/io_service_signal_unittests.cc b/src/lib/process/tests/io_service_signal_unittests.cc
index e7685485ff..4f67f8bbcd 100644
--- a/src/lib/process/tests/io_service_signal_unittests.cc
+++ b/src/lib/process/tests/io_service_signal_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -20,7 +20,7 @@ namespace process {
/// @brief Test fixture for testing the use of IOSignals.
///
/// This fixture is exercises IOSignaling as it is intended to be used in
-/// an application in conjuction with util::SignalSet.
+/// an application in conjunction with util::SignalSet.
class IOSignalTest : public ::testing::Test {
public:
/// @brief IOService instance to process IO.
@@ -126,7 +126,7 @@ public:
}
};
-// Used for constuctor tests.
+// Used for constructor tests.
void dummyHandler(IOSignalId) {
}
@@ -241,7 +241,7 @@ TEST_F(IOSignalTest, singleSignalTest) {
// Use TimedSignal to generate SIGINT 100 ms after we start IOService::run.
TimedSignal sig_int(*io_service_, SIGINT, 100);
- // The first handler executed is the IOSignal's internal timer expirey
+ // The first handler executed is the IOSignal's internal timer expire
// callback.
io_service_->run_one();
@@ -356,8 +356,8 @@ TEST_F(IOSignalTest, mixedSignals) {
// Verify we received the expected number of signals.
ASSERT_EQ(stop_at_count_, processed_signals_.size());
- // There is no gaurantee that the signals will always be delivered in the
- // order they are raised, but all of them should get delievered. Loop
+ // There is no guarantee that the signals will always be delivered in the
+ // order they are raised, but all of them should get delivered. Loop
// through and tally them up.
int sigint_cnt = 0;
int sigusr1_cnt = 0;
diff --git a/src/lib/process/testutils/d_test_stubs.cc b/src/lib/process/testutils/d_test_stubs.cc
index f643563e9b..b73f6346ef 100644
--- a/src/lib/process/testutils/d_test_stubs.cc
+++ b/src/lib/process/testutils/d_test_stubs.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,42 +9,16 @@
#include <process/d_log.h>
#include <process/spec_config.h>
#include <process/testutils/d_test_stubs.h>
+#include <cc/command_interpreter.h>
using namespace boost::asio;
namespace isc {
namespace process {
-const char* valid_d2_config = "{ "
- "\"ip-address\" : \"127.0.0.1\" , "
- "\"port\" : 5031, "
- "\"tsig-keys\": ["
- "{ \"name\": \"d2_key.tmark.org\" , "
- " \"algorithm\": \"HMAC-MD5\" ,"
- " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
- "} ],"
- "\"forward-ddns\" : {"
- "\"ddns-domains\": [ "
- "{ \"name\": \"tmark.org.\" , "
- " \"key-name\": \"d2_key.tmark.org\" , "
- " \"dns-servers\" : [ "
- " { \"ip-address\": \"127.0.0.101\" } "
- "] } ] }, "
- "\"reverse-ddns\" : {"
- "\"ddns-domains\": [ "
- "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
- " \"key-name\": \"d2_key.tmark.org\" , "
- " \"dns-servers\" : [ "
- " { \"ip-address\": \"127.0.0.101\" , "
- " \"port\": 100 } ] } "
- "] } }";
-
// Initialize the static failure flag.
SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
-// Define custom process command supported by DStubProcess.
-const char* DStubProcess::stub_proc_command_("cool_proc_cmd");
-
DStubProcess::DStubProcess(const char* name, asiolink::IOServicePtr io_service)
: DProcessBase(name, io_service, DCfgMgrBasePtr(new DStubCfgMgr())) {
};
@@ -86,36 +60,18 @@ DStubProcess::shutdown(isc::data::ConstElementPtr /* args */) {
setShutdownFlag(true);
stopIOService();
- return (isc::config::createAnswer(0, "Shutdown inititiated."));
+ return (isc::config::createAnswer(0, "Shutdown initiated."));
}
isc::data::ConstElementPtr
-DStubProcess::configure(isc::data::ConstElementPtr config_set) {
+DStubProcess::configure(isc::data::ConstElementPtr config_set, bool check_only) {
if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
// Simulates a process configure failure.
return (isc::config::createAnswer(1,
"Simulated process configuration error."));
}
- return (getCfgMgr()->parseConfig(config_set));
-}
-
-isc::data::ConstElementPtr
-DStubProcess::command(const std::string& command,
- isc::data::ConstElementPtr /* args */) {
- isc::data::ConstElementPtr answer;
- if (SimFailure::shouldFailOn(SimFailure::ftProcessCommand)) {
- // Simulates a process command execution failure.
- answer = isc::config::createAnswer(COMMAND_ERROR,
- "SimFailure::ftProcessCommand");
- } else if (command.compare(stub_proc_command_) == 0) {
- answer = isc::config::createAnswer(COMMAND_SUCCESS, "Command accepted");
- } else {
- answer = isc::config::createAnswer(COMMAND_INVALID,
- "Unrecognized command:" + command);
- }
-
- return (answer);
+ return (getCfgMgr()->parseConfig(config_set, check_only));
}
DStubProcess::~DStubProcess() {
@@ -123,9 +79,6 @@ DStubProcess::~DStubProcess() {
//************************** DStubController *************************
-// Define custom controller command supported by DStubController.
-const char* DStubController::stub_ctl_command_("spiffy");
-
// Define custom command line option command supported by DStubController.
const char* DStubController::stub_option_x_ = "x";
@@ -149,7 +102,7 @@ DStubController::instance() {
DStubController::DStubController()
: DControllerBase(stub_app_name_, stub_bin_name_),
- processed_signals_(), record_signal_only_(false) {
+ processed_signals_(), record_signal_only_(false), use_alternate_parser_(false) {
if (getenv("KEA_FROM_BUILD")) {
setSpecFileName(std::string(getenv("KEA_FROM_BUILD")) +
@@ -185,24 +138,6 @@ DProcessBase* DStubController::createProcess() {
return (new DStubProcess(getAppName().c_str(), getIOService()));
}
-isc::data::ConstElementPtr
-DStubController::customControllerCommand(const std::string& command,
- isc::data::ConstElementPtr /* args */) {
- isc::data::ConstElementPtr answer;
- if (SimFailure::shouldFailOn(SimFailure::ftControllerCommand)) {
- // Simulates command failing to execute.
- answer = isc::config::createAnswer(COMMAND_ERROR,
- "SimFailure::ftControllerCommand");
- } else if (command.compare(stub_ctl_command_) == 0) {
- answer = isc::config::createAnswer(COMMAND_SUCCESS, "Command accepted");
- } else {
- answer = isc::config::createAnswer(COMMAND_INVALID,
- "Unrecognized command:" + command);
- }
-
- return (answer);
-}
-
const std::string DStubController::getCustomOpts() const {
// Return the "list" of custom options supported by DStubController.
return (std::string(stub_option_x_));
@@ -218,6 +153,22 @@ DStubController::processSignal(int signum){
DControllerBase::processSignal(signum);
}
+isc::data::ConstElementPtr
+DStubController::parseFile(const std::string& /*file_name*/) {
+ isc::data::ConstElementPtr elements;
+ if (use_alternate_parser_) {
+ std::ostringstream os;
+
+ os << "{ \"" << getController()->getAppName()
+ << "\": " << std::endl;
+ os << "{ \"string_test\": \"alt value\" } ";
+ os << " } " << std::endl;
+ elements = isc::data::Element::fromJSON(os.str());
+ }
+
+ return (elements);
+}
+
DStubController::~DStubController() {
}
@@ -273,7 +224,7 @@ DControllerTest::runWithConfig(const std::string& config, int run_time_ms,
const_cast<char*>("-d") };
launch(4, argv);
} catch (...) {
- // calculate elasped time, then rethrow it
+ // calculate elapsed time, then rethrow it
elapsed_time = microsec_clock::universal_time() - start;
throw;
}
@@ -316,37 +267,6 @@ DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
/// @brief Defines the name of the configuration file to use
const char* DControllerTest::CFG_TEST_FILE = "d2-test-config.json";
-//************************** ObjectParser *************************
-
-ObjectParser::ObjectParser(const std::string& param_name,
- ObjectStoragePtr& object_values)
- : param_name_(param_name), object_values_(object_values) {
-}
-
-ObjectParser::~ObjectParser(){
-}
-
-void
-ObjectParser::build(isc::data::ConstElementPtr new_config) {
- if (SimFailure::shouldFailOn(SimFailure::ftElementBuild)) {
- // Simulates an error during element data parsing.
- isc_throw (DCfgMgrBaseError, "Simulated build exception");
- }
-
- value_ = new_config;
-}
-
-void
-ObjectParser::commit() {
- if (SimFailure::shouldFailOn(SimFailure::ftElementCommit)) {
- // Simulates an error while committing the parsed element data.
- throw std::runtime_error("Simulated commit exception");
- }
-
- object_values_->setParam(param_name_, value_,
- isc::data::Element::Position());
-}
-
//************************** DStubContext *************************
DStubContext::DStubContext(): object_values_(new ObjectStorage()) {
@@ -375,6 +295,11 @@ DStubContext::DStubContext(const DStubContext& rhs): DCfgContextBase(rhs),
object_values_(new ObjectStorage(*(rhs.object_values_))) {
}
+isc::data::ElementPtr
+DStubContext::toElement() const {
+ return (isc::data::Element::createMap());
+}
+
//************************** DStubCfgMgr *************************
DStubCfgMgr::DStubCfgMgr()
@@ -389,22 +314,25 @@ DStubCfgMgr::createNewContext() {
return (DCfgContextBasePtr (new DStubContext()));
}
-isc::dhcp::ParserPtr
-DStubCfgMgr::createConfigParser(const std::string& element_id,
- const isc::data::Element::Position& pos) {
- isc::dhcp::ParserPtr parser;
+void
+DStubCfgMgr::parseElement(const std::string& element_id,
+ isc::data::ConstElementPtr element) {
DStubContextPtr context
= boost::dynamic_pointer_cast<DStubContext>(getContext());
+
if (element_id == "bool_test") {
- parser.reset(new isc::dhcp::
- BooleanParser(element_id,
- context->getBooleanStorage()));
+ bool value = element->boolValue();
+ context->getBooleanStorage()->setParam(element_id, value,
+ element->getPosition());
} else if (element_id == "uint32_test") {
- parser.reset(new isc::dhcp::Uint32Parser(element_id,
- context->getUint32Storage()));
+ uint32_t value = element->intValue();
+ context->getUint32Storage()->setParam(element_id, value,
+ element->getPosition());
+
} else if (element_id == "string_test") {
- parser.reset(new isc::dhcp::StringParser(element_id,
- context->getStringStorage()));
+ std::string value = element->stringValue();
+ context->getStringStorage()->setParam(element_id, value,
+ element->getPosition());
} else {
// Fail only if SimFailure dictates we should. This makes it easier
// to test parse ordering, by permitting a wide range of element ids
@@ -412,15 +340,20 @@ DStubCfgMgr::createConfigParser(const std::string& element_id,
if (SimFailure::shouldFailOn(SimFailure::ftElementUnknown)) {
isc_throw(DCfgMgrBaseError,
"Configuration parameter not supported: " << element_id
- << pos);
+ << element->getPosition());
}
// Going to assume anything else is an object element.
- parser.reset(new ObjectParser(element_id, context->getObjectStorage()));
+ context->getObjectStorage()->setParam(element_id, element,
+ element->getPosition());
}
parsed_order_.push_back(element_id);
- return (parser);
+}
+
+isc::data::ConstElementPtr
+DStubCfgMgr::parse(isc::data::ConstElementPtr /*config*/, bool /*check_only*/) {
+ return (isc::config::createAnswer(0, "It all went fine. I promise"));
}
}; // namespace isc::process
diff --git a/src/lib/process/testutils/d_test_stubs.h b/src/lib/process/testutils/d_test_stubs.h
index 99c68a3d89..6deaa8346a 100644
--- a/src/lib/process/testutils/d_test_stubs.h
+++ b/src/lib/process/testutils/d_test_stubs.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -30,11 +30,6 @@ using namespace boost::posix_time;
namespace isc {
namespace process {
-/// @brief Provides a valid DHCP-DDNS configuration for testing basic
-/// parsing fundamentals.
-extern const char* valid_d2_config;
-
-
/// @brief Class is used to set a globally accessible value that indicates
/// a specific type of failure to simulate. Test derivations of base classes
/// can exercise error handling code paths by testing for specific SimFailure
@@ -50,8 +45,6 @@ public:
ftCreateProcessNull,
ftProcessInit,
ftProcessConfigure,
- ftControllerCommand,
- ftProcessCommand,
ftProcessShutdown,
ftElementBuild,
ftElementCommit,
@@ -109,9 +102,6 @@ public:
class DStubProcess : public DProcessBase {
public:
- /// @brief Static constant that defines a custom process command string.
- static const char* stub_proc_command_;
-
/// @brief Constructor
///
/// @param name name is a text label for the process. Generally used
@@ -150,29 +140,12 @@ public:
/// of the inbound configuration.
///
/// @param config_set a new configuration (JSON) for the process
+ /// @param check_only true if configuration is to be verified only, not applied
/// @return an Element that contains the results of configuration composed
/// of an integer status value (0 means successful, non-zero means failure),
/// and a string explanation of the outcome.
- virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
- config_set);
-
- /// @brief Executes the given command.
- ///
- /// This implementation will recognizes one "custom" process command,
- /// stub_proc_command_. It will fail if SimFailure is set to
- /// ftProcessCommand.
- ///
- /// @param command is a string label representing the command to execute.
- /// @param args is a set of arguments (if any) required for the given
- /// command.
- /// @return an Element that contains the results of command composed
- /// of an integer status value and a string explanation of the outcome.
- /// The status value is:
- /// COMMAND_SUCCESS if the command is recognized and executes successfully.
- /// COMMAND_ERROR if the command is recognized but fails to execute.
- /// COMMAND_INVALID if the command is not recognized.
- virtual isc::data::ConstElementPtr command(const std::string& command,
- isc::data::ConstElementPtr args);
+ virtual isc::data::ConstElementPtr
+ configure(isc::data::ConstElementPtr config_set, bool check_only);
/// @brief Returns configuration summary in the textual format.
///
@@ -202,10 +175,6 @@ public:
/// @return returns a pointer reference to the singleton instance.
static DControllerBasePtr& instance();
- /// @brief Defines a custom controller command string. This is a
- /// custom command supported by DStubController.
- static const char* stub_ctl_command_;
-
/// @brief Defines a custom command line option supported by
/// DStubController.
static const char* stub_option_x_;
@@ -235,6 +204,19 @@ public:
record_signal_only_ = value;
}
+ /// @brief Determines if parseFile() implementation is used
+ ///
+ /// If true, parseFile() will return a Map of elements with fixed content,
+ /// mimicking a controller which is using alternate JSON parsing.
+ /// If false, parseFile() will return an empty pointer mimicking a
+ /// controller which is using original JSON parsing supplied by the
+ /// Element class.
+ ///
+ /// @param value boolean which if true enables record-only behavior
+ void useAlternateParser(bool value) {
+ use_alternate_parser_ = value;
+ }
+
protected:
/// @brief Handles additional command line options that are supported
/// by DStubController. This implementation supports an option "-x".
@@ -257,23 +239,6 @@ protected:
/// ftCreateProcessException.
virtual DProcessBase* createProcess();
- /// @brief Executes custom controller commands are supported by
- /// DStubController. This implementation supports one custom controller
- /// command, stub_ctl_command_. It will fail if SimFailure is set
- /// to ftControllerCommand.
- ///
- /// @param command is a string label representing the command to execute.
- /// @param args is a set of arguments (if any) required for the given
- /// command.
- /// @return an Element that contains the results of command composed
- /// of an integer status value and a string explanation of the outcome.
- /// The status value is:
- /// COMMAND_SUCCESS if the command is recognized and executes successfully.
- /// COMMAND_ERROR if the command is recognized but fails to execute.
- /// COMMAND_INVALID if the command is not recognized.
- virtual isc::data::ConstElementPtr customControllerCommand(
- const std::string& command, isc::data::ConstElementPtr args);
-
/// @brief Provides a string of the additional command line options
/// supported by DStubController. DStubController supports one
/// addition option, stub_option_x_.
@@ -291,6 +256,36 @@ protected:
/// @param signum OS signal value received
virtual void processSignal(int signum);
+ /// @brief Provides alternate parse file implementation
+ ///
+ /// Overrides the base class implementation to mimick controllers which
+ /// implement alternate file parsing. If enabled via useAlternateParser()
+ /// the method will return a fixed map of elements reflecting the following
+ /// JSON:
+ ///
+ /// @code
+ /// { "<name>getController()->getAppName()" :
+ /// { "string_test": "alt value" };
+ /// }
+ ///
+ /// @endcode
+ ///
+ /// where <name> is getController()->getAppName()
+ ///
+ /// otherwise it return an empty pointer.
+ virtual isc::data::ConstElementPtr parseFile(const std::string&);
+
+public:
+
+ /// @brief Provides additional extended version text
+ ///
+ /// Overrides the base class implementation so we can
+ /// verify the getting the extended version text
+ /// contains derivaiton specific contributions.
+ virtual std::string getVersionAddendum() {
+ return ("StubController Version Text");
+ }
+
private:
/// @brief Constructor is private to protect singleton integrity.
DStubController();
@@ -301,6 +296,9 @@ private:
/// @brief Boolean for controlling if signals are merely recorded.
bool record_signal_only_;
+ /// @brief Boolean for controlling if parseFile is "implemented"
+ bool use_alternate_parser_;
+
public:
virtual ~DStubController();
};
@@ -461,18 +459,18 @@ public:
return (getController()->updateConfig(new_config));
}
- /// @Wrapper to invoke the Controller's executeCommand method. Please
- /// refer to DControllerBase::executeCommand for details.
- isc::data::ConstElementPtr executeCommand(const std::string& command,
- isc::data::ConstElementPtr args){
- return (getController()->executeCommand(command, args));
+ /// @Wrapper to invoke the Controller's checkConfig method. Please
+ /// refer to DControllerBase::checkConfig for details.
+ isc::data::ConstElementPtr checkConfig(isc::data::ConstElementPtr
+ new_config) {
+ return (getController()->checkConfig(new_config));
}
/// @brief Callback that will generate shutdown command via the
/// command callback function.
static void genShutdownCallback() {
isc::data::ElementPtr arg_set;
- getController()->executeCommand(SHUT_DOWN_COMMAND, arg_set);
+ getController()->shutdownHandler(SHUT_DOWN_COMMAND, arg_set);
}
/// @brief Callback that throws an exception.
@@ -488,7 +486,7 @@ public:
///
/// @code
/// { "<app_name>" : <content> }
- /// @endcod
+ /// @endcode
///
/// suffix the content within a JSON element with the given module
/// name or wrapped by a JSON
@@ -513,7 +511,7 @@ public:
/// file to replaced write_time_ms after DControllerBase::launch() has
/// invoked runProcess().
///
- /// @param config JSON string containing the deisred content for the config
+ /// @param config JSON string containing the desired content for the config
/// file.
/// @param write_time_ms time in milliseconds to delay before writing the
/// file.
@@ -570,51 +568,6 @@ public:
typedef isc::dhcp::ValueStorage<isc::data::ConstElementPtr> ObjectStorage;
typedef boost::shared_ptr<ObjectStorage> ObjectStoragePtr;
-/// @brief Simple parser derivation for parsing object elements.
-class ObjectParser : public isc::dhcp::DhcpConfigParser {
-public:
-
- /// @brief Constructor
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @param param_name name of the parsed parameter
- ObjectParser(const std::string& param_name, ObjectStoragePtr& object_values);
-
- /// @brief Destructor
- virtual ~ObjectParser();
-
- /// @brief Builds parameter value.
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @param new_config pointer to the new configuration
- /// @throw throws DCfgMgrBaseError if the SimFailure is set to
- /// ftElementBuild. This allows for the simulation of an
- /// exception during the build portion of parsing an element.
- virtual void build(isc::data::ConstElementPtr new_config);
-
- /// @brief Commits the parsed value to storage.
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @throw throws DCfgMgrBaseError if SimFailure is set to ftElementCommit.
- /// This allows for the simulation of an exception during the commit
- /// portion of parsing an element.
- virtual void commit();
-
-private:
- /// name of the parsed parameter
- std::string param_name_;
-
- /// pointer to the parsed value of the parameter
- isc::data::ConstElementPtr value_;
-
- /// Pointer to the storage where committed value is stored.
- ObjectStoragePtr object_values_;
-};
-
-
/// @brief Test Derivation of the DCfgContextBase class.
///
/// This class is used to test basic functionality of configuration context.
@@ -660,6 +613,11 @@ private:
/// @brief Stores non-scalar configuration elements
ObjectStoragePtr object_values_;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
};
/// @brief Defines a pointer to DStubContext.
@@ -690,20 +648,42 @@ public:
/// @brief Destructor
virtual ~DStubCfgMgr();
- /// @brief Given an element_id returns an instance of the appropriate
- /// parser. It supports the element ids as described in the class brief.
+ /// @brief Parses the given element into the appropriate object
+ ///
+ /// The method supports three named elements:
+ ///
+ /// -# "bool_test"
+ /// -# "uint32_test"
+ /// -# "string_test"
+ ///
+ /// which are parsed and whose value is then stored in the
+ /// the appropriate context value store.
///
- /// @param element_id is the string name of the element as it will appear
- /// in the configuration set.
- /// @param pos position within the configuration text (or file) of element
- /// to be parsed. This is passed for error messaging.
+ /// Any other element_id is treated generically and stored
+ /// in the context's object store, unless the simulated
+ /// error has been set to SimFailure::ftElementUnknown.
///
- /// @return returns a ParserPtr to the parser instance.
- /// @throw throws DCfgMgrBaseError if SimFailure is ftElementUnknown.
- virtual isc::dhcp::ParserPtr
- createConfigParser(const std::string& element_id,
- const isc::data::Element::Position& pos
- = isc::data::Element::Position());
+ /// @param element_id name of the element to parse
+ /// @param element Element to parse
+ ///
+ /// @throw DCfgMgrBaseError if simulated error is set
+ /// to ftElementUnknown and element_id is not one of
+ /// the named elements.
+ virtual void parseElement(const std::string& element_id,
+ isc::data::ConstElementPtr element);
+
+ /// @brief Pretends to parse the config
+ ///
+ /// This method pretends to parse the configuration specified on input
+ /// and returns a positive answer. The check_only flag is currently ignored.
+ ///
+ /// @param config configuration specified
+ /// @param check_only whether it's real configuration (false) or just
+ /// configuration check (true)
+ /// @return always positive answer
+ ///
+ isc::data::ConstElementPtr
+ parse(isc::data::ConstElementPtr config, bool check_only);
/// @brief Returns a summary of the configuration in the textual format.
///
@@ -858,10 +838,6 @@ private:
asiolink::IntervalTimerPtr timer_;
};
-/// @brief Defines a small but valid DHCP-DDNS compliant configuration for
-/// testing configuration parsing fundamentals.
-extern const char* valid_d2_config;
-
}; // namespace isc::process
}; // namespace isc
diff --git a/src/lib/stats/stats_mgr.cc b/src/lib/stats/stats_mgr.cc
index cde8a5f8e0..af8aafaf5c 100644
--- a/src/lib/stats/stats_mgr.cc
+++ b/src/lib/stats/stats_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -59,19 +59,19 @@ void StatsMgr::addValue(const std::string& name, const std::string& value) {
ObservationPtr StatsMgr::getObservation(const std::string& name) const {
/// @todo: Implement contexts.
- // Currently we keep everyting in a global context.
+ // Currently we keep everything in a global context.
return (global_->get(name));
}
void StatsMgr::addObservation(const ObservationPtr& stat) {
/// @todo: Implement contexts.
- // Currently we keep everyting in a global context.
+ // Currently we keep everything in a global context.
return (global_->add(stat));
}
bool StatsMgr::deleteObservation(const std::string& name) {
/// @todo: Implement contexts.
- // Currently we keep everyting in a global context.
+ // Currently we keep everything in a global context.
return (global_->del(name));
}
diff --git a/src/lib/stats/stats_mgr.h b/src/lib/stats/stats_mgr.h
index c5e5c7abc0..554e309e91 100644
--- a/src/lib/stats/stats_mgr.h
+++ b/src/lib/stats/stats_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -28,7 +28,7 @@ namespace stats {
/// As of May 2015, Tomek ran performance benchmarks (see unit-tests in
/// stats_mgr_unittest.cc with performance in their names) and it seems
/// the code is able to register ~2.5-3 million observations per second, even
-/// with 1000 different statistics recored. That seems sufficient for now,
+/// with 1000 different statistics recorded. That seems sufficient for now,
/// so there is no immediate need to develop any multi-threading solutions
/// for now. However, should this decision be revised in the future, the
/// best place for it would to be modify @ref addObservation method here.
@@ -47,7 +47,7 @@ namespace stats {
/// significantly. While it's possible to log on sufficiently high debug
/// level, such a log would be not that useful)
/// - dependency (statistics are intended to be a lightweight library, adding
-/// dependency on libkea-log, which has its own dependecies, including
+/// dependency on libkea-log, which has its own dependencies, including
/// external log4cplus, is against 'lightweight' design)
/// - if logging of specific statistics is warranted, it is recommended to
/// add log entries in the code that calls StatsMgr.
diff --git a/src/lib/stats/tests/observation_unittest.cc b/src/lib/stats/tests/observation_unittest.cc
index 745f25eb86..d39173bc09 100644
--- a/src/lib/stats/tests/observation_unittest.cc
+++ b/src/lib/stats/tests/observation_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -46,7 +46,7 @@ public:
Observation d;
};
-// Basic tests for the Obseration constructors. This test checks whether
+// Basic tests for the Observation constructors. This test checks whether
// parameters passed to the constructor initialize the object properly.
TEST_F(ObservationTest, constructor) {
@@ -139,7 +139,7 @@ TEST_F(ObservationTest, timers) {
ptime before = microsec_clock::local_time();
b.setValue(123.0); // Set it to a random value and record the time.
- // Allow a bit of inprecision. This test allows 50ms. That should be ok,
+ // Allow a bit of imprecision. This test allows 50ms. That should be ok,
// when running on virtual machines.
ptime after = before + millisec::time_duration(0,0,0,50);
diff --git a/src/lib/stats/tests/stats_mgr_unittest.cc b/src/lib/stats/tests/stats_mgr_unittest.cc
index 2cc928ab65..782cb7c509 100644
--- a/src/lib/stats/tests/stats_mgr_unittest.cc
+++ b/src/lib/stats/tests/stats_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -453,7 +453,7 @@ TEST_F(StatsMgrTest, commandStatisticGetNegative) {
EXPECT_EQ("{ \"arguments\": { }, \"result\": 0 }", rsp->str());
}
-// This test checks whether statistc-get-all command returns all statistics
+// This test checks whether statistic-get-all command returns all statistics
// correctly.
TEST_F(StatsMgrTest, commandGetAll) {
diff --git a/src/lib/testutils/Makefile.am b/src/lib/testutils/Makefile.am
index 8aa2cc2c71..cfd0c8d058 100644
--- a/src/lib/testutils/Makefile.am
+++ b/src/lib/testutils/Makefile.am
@@ -9,6 +9,7 @@ noinst_LTLIBRARIES = libkea-testutils.la
libkea_testutils_la_SOURCES = io_utils.cc io_utils.h
libkea_testutils_la_SOURCES += log_utils.cc log_utils.h
+libkea_testutils_la_SOURCES += test_to_element.cc test_to_element.h
libkea_testutils_la_SOURCES += unix_control_client.h unix_control_client.cc
libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
libkea_testutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
diff --git a/src/lib/testutils/dhcp_test_lib.sh.in b/src/lib/testutils/dhcp_test_lib.sh.in
index 3eadbc2de2..f490053b93 100644
--- a/src/lib/testutils/dhcp_test_lib.sh.in
+++ b/src/lib/testutils/dhcp_test_lib.sh.in
@@ -1,11 +1,11 @@
-# Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# A list of Kea processes, mainly used by the cleanup functions.
-KEA_PROCS="kea-dhcp4 kea-dhcp6 kea-dhcp-ddns"
+KEA_PROCS="kea-dhcp4 kea-dhcp6 kea-dhcp-ddns kea-ctrl-agent"
### Logging functions ###
@@ -88,7 +88,7 @@ assert_string_contains() {
fi
}
-# Begins a test by prining its name.
+# Begins a test by printing its name.
test_start() {
TEST_NAME=${1}
if [ -z ${TEST_NAME} ]; then
@@ -155,7 +155,7 @@ configuration"
# ${LOG_FILE}.
set_logger() {
if [ -z ${LOG_FILE} ]; then
- test_lib_error "set_logger requies LOG_FILE variable be set"
+ test_lib_error "set_logger requires LOG_FILE variable be set"
clean_exit 1
fi
printf "Kea log will be stored in %s.\n" ${LOG_FILE}
@@ -163,7 +163,7 @@ set_logger() {
}
# PID file path is by default <kea-install-dir>/var/kea, but can be
-# overriden by the environmental variable.
+# overridden by the environmental variable.
PID_FILE_PATH=@localstatedir@/@PACKAGE@/
if [ ! -z ${KEA_PIDFILE_DIR} ]; then
PID_FILE_PATH="${KEA_PIDFILE_DIR}"
@@ -174,7 +174,7 @@ fi
# This function uses PID file to obtain the PID and then calls
# 'kill -0 <pid>' to check if the process is alive.
# The PID files are expected to be located in the ${PID_FILE_PATH},
-# and their names should match the follwing pattern:
+# and their names should match the following pattern:
# <cfg_file_name>.<proc_name>.pid. If the <cfg_file_name> is not
# specified a 'test_config' is used by default.
#
@@ -261,8 +261,8 @@ get_log_messages() {
# _GET_RECONFIGS: number of configurations so far.
# _GET_RECONFIG_ERRORS: number of configuration errors.
get_reconfigs() {
- # Grep log file for CONFIG_COMPLETE occurences. There should
- # be one occurence per (re)configuration.
+ # Grep log file for CONFIG_COMPLETE occurrences. There should
+ # be one occurrence per (re)configuration.
_GET_RECONFIGS=$( grep -o CONFIG_COMPLETE ${LOG_FILE} | wc -w )
# Grep log file for CONFIG_LOAD_FAIL to check for configuration
# failures.
@@ -276,7 +276,7 @@ get_reconfigs() {
# It shuts down running Kea processes and removes temporary files.
# The location of the log file and the configuration files should be set
# in the ${LOG_FILE}, ${CFG_FILE} and ${KEACTRL_CFG_FILE} variables
-# recpectively, prior to calling this function.
+# respectively, prior to calling this function.
cleanup() {
# If there is no KEA_PROCS set, just return
@@ -291,7 +291,7 @@ cleanup() {
get_pid ${proc_name}
# Shut down running Kea process.
if [ ${_GET_PIDS_NUM} -ne 0 ]; then
- printf "Shutting down Kea proccess having pid %d.\n" ${_GET_PID}
+ printf "Shutting down Kea process having pid %d.\n" ${_GET_PID}
kill -9 ${_GET_PID}
fi
done
@@ -307,6 +307,7 @@ cleanup() {
# Remove temporary files.
rm -rf ${LOG_FILE}
+ rm -rf ${LOG_FILE}.lock
# Use asterisk to remove all files starting with the given name,
# in case the LFC has been run. LFC creates files with postfixes
# appended to the lease file name.
@@ -318,7 +319,7 @@ cleanup() {
}
# Exists the test in the clean way.
-# It peformes the cleanup and prints whether the test has passed or failed.
+# It performs the cleanup and prints whether the test has passed or failed.
# If a test fails, the Kea log is dumped.
clean_exit() {
exit_code=${1} # Exit code to be returned by the exit function.
@@ -344,7 +345,7 @@ start_kea() {
}
# Waits with timeout for Kea to start.
-# This function repeatedly checs if the Kea log file has been created
+# This function repeatedly checks if the Kea log file has been created
# and is non-empty. If it is, the function assumes that Kea has started.
# It doesn't check the contents of the log file though.
# If the log file doesn't exist the function sleeps for a second and
@@ -384,7 +385,7 @@ wait_for_kea() {
# of message occurrences to show up. If the expected number of
# message doesn't occur, the error status is returned.
# Return value:
-# _WAIT_FOR_MESSAGE: 0 if the message hasn't occured, 1 otherwise.
+# _WAIT_FOR_MESSAGE: 0 if the message hasn't occurred, 1 otherwise.
wait_for_message() {
local timeout=${1} # Expected timeout value in seconds.
local message="${2}" # Expected message id.
@@ -435,7 +436,7 @@ must be a number"
# Waits for server to be down.
# Return value:
-# _WAIT_FOR_SERVER_DOWN: 1 if server is down, 0 if timeout occured and the
+# _WAIT_FOR_SERVER_DOWN: 1 if server is down, 0 if timeout occurred and the
# server is still running.
wait_for_server_down() {
local timeout=${1} # Timeout specified in seconds.
diff --git a/src/lib/testutils/io_utils.cc b/src/lib/testutils/io_utils.cc
index 54aff4cb6f..0081707e97 100644
--- a/src/lib/testutils/io_utils.cc
+++ b/src/lib/testutils/io_utils.cc
@@ -1,9 +1,10 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#include <exceptions/exceptions.h>
#include <testutils/io_utils.h>
#include <gtest/gtest.h>
#include <fstream>
@@ -11,14 +12,11 @@
#include <string>
namespace isc {
-namespace dhcp {
namespace test {
bool fileExists(const std::string& file_path) {
- std::ifstream fs(file_path.c_str());
- const bool file_exists = fs.good();
- fs.close();
- return (file_exists);
+ struct stat statbuf;
+ return(stat(file_path.c_str(), &statbuf) == 0);
}
std::string readFile(const std::string& file_path) {
@@ -38,7 +36,77 @@ std::string readFile(const std::string& file_path) {
return (output.str());
}
-}; // end of isc::dhcp::test namespace
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+std::string decommentJSONfile(const std::string& input_file) {
+
+ using namespace std;
+
+ ifstream f(input_file);
+ if (!f.is_open()) {
+ isc_throw(isc::BadValue, "can't open input file for reading: " + input_file);
+ }
+
+ string outfile;
+ size_t last_slash = input_file.find_last_of("/");
+ if (last_slash != string::npos) {
+ outfile = input_file.substr(last_slash + 1);
+ } else {
+ outfile = input_file;
+ }
+ outfile += "-decommented";
+
+ ofstream out(outfile);
+ if (!out.is_open()) {
+ isc_throw(isc::BadValue, "can't open output file for writing: " + input_file);
+ }
+
+ bool in_comment = false;
+ string line;
+ while (std::getline(f, line)) {
+ // First, let's get rid of the # comments
+ size_t hash_pos = line.find("#");
+ if (hash_pos != string::npos) {
+ line = line.substr(0, hash_pos);
+ }
+ // Second, let's get rid of the // comments
+ size_t dblslash_pos = line.find("//");
+ if (dblslash_pos != string::npos) {
+ line = line.substr(0, dblslash_pos);
+ }
+
+ // Now the tricky part: c comments.
+ size_t begin_pos = line.find("/*");
+ size_t end_pos = line.find("*/");
+ if (in_comment && end_pos == string::npos) {
+ // we continue through multiline comment
+ line = "";
+ } else {
+
+ if (begin_pos != string::npos) {
+ in_comment = true;
+ if (end_pos != string::npos) {
+ // single line comment. Let's get rid of the content in between
+ line = line.replace(begin_pos, end_pos + 2, end_pos + 2 - begin_pos, ' ');
+ in_comment = false;
+ } else {
+ line = line.substr(0, begin_pos);
+ }
+ } else {
+ if (in_comment && end_pos != string::npos) {
+ line = line.replace(0, end_pos +2 , end_pos + 2, ' ');
+ in_comment = false;
+ }
+ }
+ }
+
+ // Finally, write the line to the output file.
+ out << line << endl;
+ }
+ f.close();
+ out.close();
+
+ return (outfile);
+}
+
+}; // end of isc::test namespace
+}; // end of isc namespace
diff --git a/src/lib/testutils/io_utils.h b/src/lib/testutils/io_utils.h
index 8cf1ef6e14..ce4b57ccf2 100644
--- a/src/lib/testutils/io_utils.h
+++ b/src/lib/testutils/io_utils.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,9 +8,9 @@
#define TEST_IO_UTILS_H
#include <string>
+#include <sys/stat.h>
namespace isc {
-namespace dhcp {
namespace test {
/// @brief Checks if specified file exists.
@@ -25,8 +25,21 @@ bool fileExists(const std::string& file_path);
/// @return File contents.
std::string readFile(const std::string& file_path);
-}; // end of isc::dhcp::test namespace
-}; // end of isc::dhcp namespace
+/// @brief Removes comments from a JSON file
+///
+/// Removes //, # and /* */ comments from the input file and writes its content
+/// to another file. The comments are replaced with spaces, so the original
+/// token locations should remain unaffected. This is rather naive
+/// implementation, but it's probably sufficient for testing. It won't be able
+/// to pick any trickier cases, like # or // appearing in strings, nested C++
+/// comments etc.
+///
+/// @param input_file file to be stripped of comments
+/// @return filename of a new file that has comments stripped from it
+/// @throw BadValue if the input file cannot be opened
+std::string decommentJSONfile(const std::string& input_file);
+
+}; // end of isc::test namespace
}; // end of isc namespace
#endif // TEST_IO_UTILS_H
diff --git a/src/lib/testutils/test_to_element.cc b/src/lib/testutils/test_to_element.cc
new file mode 100644
index 0000000000..115757a247
--- /dev/null
+++ b/src/lib/testutils/test_to_element.cc
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <testutils/test_to_element.h>
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace test {
+
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+std::string generateDiff(std::string left, std::string right) {
+ std::vector<std::string> left_lines;
+ boost::split(left_lines, left, boost::is_any_of("\n"));
+ std::vector<std::string> right_lines;
+ boost::split(right_lines, right, boost::is_any_of("\n"));
+ using namespace testing::internal;
+ return (edit_distance::CreateUnifiedDiff(left_lines, right_lines));
+}
+#else
+std::string generateDiff(std::string, std::string) {
+ return ("");
+}
+#endif
+
+}; // end of isc::test namespace
+}; // end of isc namespace
diff --git a/src/lib/testutils/test_to_element.h b/src/lib/testutils/test_to_element.h
new file mode 100644
index 0000000000..1aa88fc001
--- /dev/null
+++ b/src/lib/testutils/test_to_element.h
@@ -0,0 +1,91 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TEST_TO_ELEMENT_H
+#define TEST_TO_ELEMENT_H
+
+#include <cc/data.h>
+#include <cc/cfg_to_element.h>
+#include <gtest/gtest.h>
+#include <string>
+#ifdef HAVE_IS_BASE_OF
+#include <type_traits>
+#endif
+
+#ifndef CONFIG_H_WAS_INCLUDED
+#error config.h must be included before test_to_element.h
+#endif
+
+namespace isc {
+namespace test {
+
+/// @brief Return the difference between two strings
+///
+/// Use the gtest >= 1.8.0 tool which builds the difference between
+/// two vectors of lines.
+///
+/// @param left left string
+/// @param right right string
+/// @return the unified diff between left and right
+std::string generateDiff(std::string left, std::string right);
+
+/// @brief Run a test using toElement() method with a string
+///
+/// @tparam Cfg the class implementing the toElement() method
+/// @param expected the expected textual value
+/// @param cfg an instance of the Cfg class
+template<typename Cfg>
+void runToElementTest(const std::string& expected, const Cfg& cfg) {
+ using namespace isc::data;
+#ifdef HAVE_IS_BASE_OF
+ static_assert(std::is_base_of<CfgToElement, Cfg>::value,
+ "CfgToElement is not a base of the template parameter");
+#endif
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(expected)) << expected;
+ ConstElementPtr unparsed;
+ ASSERT_NO_THROW(unparsed = cfg.toElement());
+ if (!isEquivalent(json, unparsed)) {
+ std::string wanted = prettyPrint(json);
+ std::string got = prettyPrint(unparsed);
+ ADD_FAILURE() << "Expected:\n" << wanted << "\n"
+ << "Actual:\n" << got
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+ << "\nDiff:\n" << generateDiff(wanted, got)
+#endif
+ << "\n";
+ }
+}
+
+/// @brief Run a test using toElement() method with an Element
+///
+/// @tparam Cfg the class implementing the toElement() method
+/// @param expected the expected element value
+/// @param cfg an instance of the Cfg class
+template<typename Cfg>
+void runToElementTest(isc::data::ConstElementPtr expected, const Cfg& cfg) {
+#ifdef HAVE_IS_BASE_OF
+ static_assert(std::is_base_of<isc::data::CfgToElement, Cfg>::value,
+ "CfgToElement is not a base of the template parameter");
+#endif
+ isc::data::ConstElementPtr unparsed;
+ ASSERT_NO_THROW(unparsed = cfg.toElement());
+ if (!isEquivalent(expected, unparsed)) {
+ std::string wanted = prettyPrint(expected);
+ std::string got = prettyPrint(unparsed);
+ ADD_FAILURE() << "Expected:\n" << wanted << "\n"
+ << "Actual:\n" << got
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+ << "\nDiff:\n" << generateDiff(wanted, got)
+#endif
+ << "\n";
+ }
+}
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // TEST_TO_ELEMENT_H
diff --git a/src/lib/testutils/unix_control_client.cc b/src/lib/testutils/unix_control_client.cc
index 8057258b6b..faae202b6e 100644
--- a/src/lib/testutils/unix_control_client.cc
+++ b/src/lib/testutils/unix_control_client.cc
@@ -84,18 +84,18 @@ bool UnixControlClient::sendCommand(const std::string& command) {
return (true);
}
-bool UnixControlClient::getResponse(std::string& response) {
+bool UnixControlClient::getResponse(std::string& response,
+ const unsigned int timeout_sec) {
// Receive response
char buf[65536];
memset(buf, 0, sizeof(buf));
- switch (selectCheck()) {
+ switch (selectCheck(timeout_sec)) {
case -1: {
const char* errmsg = strerror(errno);
ADD_FAILURE() << "getResponse - select failed: " << errmsg;
return (false);
}
case 0:
- ADD_FAILURE() << "No response data sent";
return (false);
default:
@@ -110,17 +110,12 @@ bool UnixControlClient::getResponse(std::string& response) {
return (false);
}
- if (bytes_rcvd >= sizeof(buf)) {
- ADD_FAILURE() << "Response size too large: " << bytes_rcvd;
- return (false);
- }
-
// Convert the response to a string
response = std::string(buf, bytes_rcvd);
return (true);
}
-int UnixControlClient::selectCheck() {
+int UnixControlClient::selectCheck(const unsigned int timeout_sec) {
int maxfd = 0;
fd_set read_fds;
@@ -131,7 +126,7 @@ int UnixControlClient::selectCheck() {
maxfd = socket_fd_;
struct timeval select_timeout;
- select_timeout.tv_sec = 0;
+ select_timeout.tv_sec = static_cast<time_t>(timeout_sec);
select_timeout.tv_usec = 0;
return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
diff --git a/src/lib/testutils/unix_control_client.h b/src/lib/testutils/unix_control_client.h
index d76772ec7f..8060d975c8 100644
--- a/src/lib/testutils/unix_control_client.h
+++ b/src/lib/testutils/unix_control_client.h
@@ -44,13 +44,16 @@ public:
/// @brief Reads the response text from the open Control Channel
/// @param response variable into which the received response should be
/// placed.
+ /// @param timeout_sec Timeout for receiving response in seconds.
/// @return true if data was successfully read from the socket,
/// false otherwise
- bool getResponse(std::string& response);
+ bool getResponse(std::string& response, const unsigned int timeout_sec = 0);
/// @brief Uses select to poll the Control Channel for data waiting
+ ///
+ /// @param timeout_sec Select timeout in seconds
/// @return -1 on error, 0 if no data is available, 1 if data is ready
- int selectCheck();
+ int selectCheck(const unsigned int timeout_sec);
/// @brief Retains the fd of the open socket
int socket_fd_;
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
index e897fe743f..64af9ebf0e 100644
--- a/src/lib/util/Makefile.am
+++ b/src/lib/util/Makefile.am
@@ -39,7 +39,7 @@ libkea_util_la_SOURCES += random/random_number_generator.h
libkea_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
-libkea_util_la_LDFLAGS = -no-undefined -version-info 2:0:0
+libkea_util_la_LDFLAGS = -no-undefined -version-info 2:1:0
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/csv_file.h b/src/lib/util/csv_file.h
index 03a4783748..8f4422974e 100644
--- a/src/lib/util/csv_file.h
+++ b/src/lib/util/csv_file.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -391,7 +391,7 @@ public:
/// true, the file will be opened and the internal pointer will be set
/// to the end of file.
///
- /// @param seek_to_end A boolean value which indicates if the intput and
+ /// @param seek_to_end A boolean value which indicates if the input and
/// output file pointer should be set at the end of file.
///
/// @throw CSVFileError when IO operation fails.
diff --git a/src/lib/util/python/const2hdr.py b/src/lib/util/python/const2hdr.py
index 59f9bfb787..1a2b7b9336 100644
--- a/src/lib/util/python/const2hdr.py
+++ b/src/lib/util/python/const2hdr.py
@@ -41,7 +41,7 @@ with open(filename_in) as file_in, open(filename_out, "w") as file_out:
"\n" +
"// \\file " + filename_out + "\n" +
'''// \\brief Common shared constants\n
-// This file contains common definitions of constasts used across the sources.
+// This file contains common definitions of constants used across the sources.
// It includes, but is not limited to the definitions of messages sent from
// one process to another. Since the names should be self-explanatory and
// the variables here are used mostly to synchronize the same values across
diff --git a/src/lib/util/python/gen_wiredata.py.in b/src/lib/util/python/gen_wiredata.py.in
index c796971807..4c9361285e 100644
--- a/src/lib/util/python/gen_wiredata.py.in
+++ b/src/lib/util/python/gen_wiredata.py.in
@@ -966,7 +966,7 @@ class NSECBASE(RR):
representation of the bitmap field. For example, for a bitmap
where the 7th and 15th bits (and only these bits) are set, it
must be '0101'. Note also that the value must be quoted with
- single quatations because it could also be interpreted as an
+ single quotations because it could also be interpreted as an
integer.
'''
nbitmap = 1 # number of bitmaps
diff --git a/src/lib/util/range_utilities.h b/src/lib/util/range_utilities.h
index c35ff69d34..f11f0bc1cd 100644
--- a/src/lib/util/range_utilities.h
+++ b/src/lib/util/range_utilities.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -39,7 +39,7 @@ isRangeZero(Iterator begin, Iterator end) {
/// after every start of your process. Calling srand() is enough. This
/// method uses default rand(), which is usually a LCG pseudo-random
/// number generator, so it is not suitable for security
-/// purposes. Please get a decent PRNG implementation, like Mersene
+/// purposes. Please get a decent PRNG implementation, like Mersenne
/// twister, if you are doing anything related with security.
///
/// PRNG initialization is left out of this function on purpose. It may
diff --git a/src/lib/util/state_model.cc b/src/lib/util/state_model.cc
index 80c163b32e..dc647b1e1f 100644
--- a/src/lib/util/state_model.cc
+++ b/src/lib/util/state_model.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -84,7 +84,7 @@ StateModel::~StateModel(){
void
StateModel::startModel(const int start_state) {
- // Intialize dictionaries of events and states.
+ // Initialize dictionaries of events and states.
initDictionaries();
// Set the current state to starting state and enter the run loop.
@@ -232,7 +232,7 @@ StateModel::verifyStates() {
getState(END_ST);
}
-void
+void
StateModel::onModelFailure(const std::string&) {
// Empty implementation to make deriving classes simpler.
}
diff --git a/src/lib/util/state_model.h b/src/lib/util/state_model.h
index 832670b02d..a4a67db578 100644
--- a/src/lib/util/state_model.h
+++ b/src/lib/util/state_model.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -90,7 +90,7 @@ public:
/// @brief Adds a state definition to the set of states.
///
/// @param value is the numeric value of the state
- /// @param label is the text label to assig to the state
+ /// @param label is the text label to assign to the state
/// @param handler is the bound instance method which handles the state's
///
/// @throw StateModelError if the value is already defined in the set, or
@@ -265,7 +265,7 @@ public:
/// @brief Begins execution of the model.
///
/// This method invokes initDictionaries method to initialize the event
- /// and state dictionaries and then starts the model execution setting
+ /// and state dictionaries and then starts the model execution setting
/// the current state to the given start state, and the event to START_EVT.
///
/// @param start_state is the state in which to begin execution.
@@ -314,7 +314,7 @@ protected:
/// @brief Initializes the event and state dictionaries.
///
/// This method invokes the define and verify methods for both events and
- /// states to initialize their respective dictionaries.
+ /// states to initialize their respective dictionaries.
///
/// @throw StateModelError or others indirectly, as this method calls
/// dictionary define and verify methods.
diff --git a/src/lib/util/stopwatch_impl.h b/src/lib/util/stopwatch_impl.h
index 78e45fda9a..3c1ee9cfb4 100644
--- a/src/lib/util/stopwatch_impl.h
+++ b/src/lib/util/stopwatch_impl.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -93,7 +93,7 @@ protected:
///
/// This method is used internally by the @c StopwatchImpl class and
/// its derivations. This class simply returns the value of
- /// @c boost::posix_time::micrisec_clock::univeral_time(), which is
+ /// @c boost::posix_time::microsec_clock::univeral_time(), which is
/// a current timestamp. The derivations may replace it with the
/// custom implementations. The typical use case is for the unit tests
/// to customize the behavior of this function to return well known
diff --git a/src/lib/util/strutil.cc b/src/lib/util/strutil.cc
index 208d158346..b3f576be95 100644
--- a/src/lib/util/strutil.cc
+++ b/src/lib/util/strutil.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -61,29 +61,68 @@ trim(const string& instring) {
// another dependency on a Boost library.
vector<string>
-tokens(const std::string& text, const std::string& delim) {
+tokens(const std::string& text, const std::string& delim, bool escape) {
vector<string> result;
-
- // Search for the first non-delimiter character
- size_t start = text.find_first_not_of(delim);
- while (start != string::npos) {
-
- // Non-delimiter found, look for next delimiter
- size_t end = text.find_first_of(delim, start);
- if (end != string::npos) {
-
- // Delimiter found, so extract string & search for start of next
- // non-delimiter segment.
- result.push_back(text.substr(start, (end - start)));
- start = text.find_first_not_of(delim, end);
-
+ string token;
+ bool in_token = false;
+ bool escaped = false;
+ for (auto c = text.cbegin(); c != text.cend(); ++c) {
+ if (delim.find(*c) != string::npos) {
+ // Current character is a delimiter
+ if (!in_token) {
+ // Two or more delimiters, eat them
+ } else if (escaped) {
+ // Escaped delimiter in a token: reset escaped and keep it
+ escaped = false;
+ token.push_back(*c);
+ } else {
+ // End of the current token: save it if not empty
+ if (!token.empty()) {
+ result.push_back(token);
+ }
+ // Reset state
+ in_token = false;
+ token.clear();
+ }
+ } else if (escape && (*c == '\\')) {
+ // Current character is the escape character
+ if (!in_token) {
+ // The escape character is the first character of a new token
+ in_token = true;
+ }
+ if (escaped) {
+ // Escaped escape: reset escaped and keep one character
+ escaped = false;
+ token.push_back(*c);
+ } else {
+ // Remember to keep the next character
+ escaped = true;
+ }
} else {
-
- // End of string found, extract rest of string and flag to exit
- result.push_back(text.substr(start));
- start = string::npos;
+ // Not a delimiter nor an escape
+ if (!in_token) {
+ // First character of a new token
+ in_token = true;
+ }
+ if (escaped) {
+ // Escaped common character: as escape was false
+ escaped = false;
+ token.push_back('\\');
+ token.push_back(*c);
+ } else {
+ // The common case: keep it
+ token.push_back(*c);
+ }
}
}
+ // End of input: close and save the current token if not empty
+ if (escaped) {
+ // Pending escape
+ token.push_back('\\');
+ }
+ if (!token.empty()) {
+ result.push_back(token);
+ }
return (result);
}
diff --git a/src/lib/util/strutil.h b/src/lib/util/strutil.h
index bacc828de5..1dd5de01de 100644
--- a/src/lib/util/strutil.h
+++ b/src/lib/util/strutil.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -69,6 +69,8 @@ std::string trim(const std::string& instring);
/// invisible leading and trailing delimiter characters. Therefore both cases
/// reduce to a set of contiguous delimiters, which are considered a single
/// delimiter (so getting rid of the string).
+/// Optional escape allows to escape delimiter characters (and *only* them
+/// and the escape character itself) using backslash.
///
/// We could use Boost for this, but this (simple) function eliminates one
/// dependency in the code.
@@ -76,10 +78,12 @@ std::string trim(const std::string& instring);
/// \param text String to be split. Passed by value as the internal copy is
/// altered during the processing.
/// \param delim Delimiter characters
+/// \param escape Use backslash to escape delimiter characters
///
/// \return Vector of tokens.
std::vector<std::string> tokens(const std::string& text,
- const std::string& delim = std::string(" \t\n"));
+ const std::string& delim = std::string(" \t\n"),
+ bool escape = false);
/// \brief Uppercase Character
diff --git a/src/lib/util/tests/csv_file_unittest.cc b/src/lib/util/tests/csv_file_unittest.cc
index 5a4f2157bd..e0a70ea36f 100644
--- a/src/lib/util/tests/csv_file_unittest.cc
+++ b/src/lib/util/tests/csv_file_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -122,7 +122,7 @@ TEST(CSVRow, trim) {
EXPECT_EQ("two", row.readAt(2));
EXPECT_EQ("three", row.readAt(3));
- // Verfiy we can trim more than one
+ // Verify we can trim more than one
ASSERT_NO_THROW(row.trim(2));
ASSERT_EQ(2, row.getValuesCount());
EXPECT_EQ("zero", row.readAt(0));
diff --git a/src/lib/util/tests/process_spawn_unittest.cc b/src/lib/util/tests/process_spawn_unittest.cc
index 30533730f4..6035a2f606 100644
--- a/src/lib/util/tests/process_spawn_unittest.cc
+++ b/src/lib/util/tests/process_spawn_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -67,7 +67,7 @@ bool waitForProcess(const ProcessSpawn& process, const pid_t pid,
///
/// This method does not use any sleep() calls, but rather iterates through
/// the loop very fast. This is not recommended in general, but is necessary
-/// to avoid updating errno by sleep() after receving a signal.
+/// to avoid updating errno by sleep() after receiving a signal.
///
/// Note: the timeout is only loosely accurate. Depending on the fraction
/// of second it was started on, it may terminate later by up to almost
@@ -90,6 +90,21 @@ bool waitForProcessFast(const ProcessSpawn& process, const pid_t pid,
return (true);
}
+// This test verifies that if the thread calling spawn has SIGCHLD
+// already block ProcessSpawnError is thrown (@todo the second error
+// case: fork() failing)
+TEST(ProcessSpawn, sigchldBlocked) {
+ std::vector<std::string> args;
+ ProcessSpawn process(getApp(), args);
+ sigset_t sset;
+ sigemptyset(&sset);
+ sigaddset(&sset, SIGCHLD);
+ sigset_t osset;
+ pthread_sigmask(SIG_BLOCK, &sset, &osset);
+ EXPECT_THROW(process.spawn(), ProcessSpawnError);
+ sigprocmask(SIG_SETMASK, &osset, 0);
+}
+
// This test verifies that the external application can be ran with
// arguments and that the exit code is gathered.
TEST(ProcessSpawn, spawnWithArgs) {
@@ -224,7 +239,7 @@ TEST(ProcessSpawn, errnoInvariance) {
EXPECT_EQ(64, process.getExitStatus(pid));
- // errno value should be set to be preserved, despite the SIGCHILD
+ // errno value should be set to be preserved, despite the SIGCHLD
// handler (ProcessSpawnImpl::waitForProcess) calling waitpid(), which
// will likely set errno to ECHILD. See trac4000.
EXPECT_EQ(123, errno);
diff --git a/src/lib/util/tests/staged_value_unittest.cc b/src/lib/util/tests/staged_value_unittest.cc
index bac6161481..bcfc6771fd 100644
--- a/src/lib/util/tests/staged_value_unittest.cc
+++ b/src/lib/util/tests/staged_value_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,7 +15,7 @@ using namespace isc::util;
// This test verifies that the value can be assigned and committed.
TEST(StagedValueTest, assignAndCommit) {
- // Initally the value should be set to a default
+ // Initially the value should be set to a default
StagedValue<int> value;
ASSERT_EQ(0, value.getValue());
diff --git a/src/lib/util/tests/state_model_unittest.cc b/src/lib/util/tests/state_model_unittest.cc
index 5dfcd7be36..cde06b67f5 100644
--- a/src/lib/util/tests/state_model_unittest.cc
+++ b/src/lib/util/tests/state_model_unittest.cc
@@ -218,7 +218,7 @@ public:
/// @brief Manually construct the event and state dictionaries.
/// This allows testing without running startModel.
- void initDictionaires() {
+ void initDictionaries() {
ASSERT_NO_THROW(defineEvents());
ASSERT_NO_THROW(verifyEvents());
ASSERT_NO_THROW(defineStates());
@@ -483,7 +483,7 @@ TEST_F(StateModelTest, runBeforeStart) {
/// a normal conclusion.
TEST_F(StateModelTest, transitionWithEnd) {
// Init dictionaries manually, normally done by startModel.
- initDictionaires();
+ initDictionaries();
// call transition to move from NEW_ST to DUMMY_ST with START_EVT
EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
@@ -508,7 +508,7 @@ TEST_F(StateModelTest, transitionWithEnd) {
/// failed conclusion.
TEST_F(StateModelTest, transitionWithAbort) {
// Init dictionaries manually, normally done by startModel.
- initDictionaires();
+ initDictionaries();
// call transition to move from NEW_ST to DUMMY_ST with START_EVT
EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
@@ -533,7 +533,7 @@ TEST_F(StateModelTest, transitionWithAbort) {
/// work properly.
TEST_F(StateModelTest, doFlags) {
// Init dictionaries manually, normally done by startModel.
- initDictionaires();
+ initDictionaries();
// Verify that "do" flags are false.
EXPECT_FALSE(doOnEntry());
@@ -564,7 +564,7 @@ TEST_F(StateModelTest, doFlags) {
/// the model is running but not after.
TEST_F(StateModelTest, statusMethods) {
// Init dictionaries manually, normally done by startModel.
- initDictionaires();
+ initDictionaries();
// After construction, state model is "new", all others should be false.
EXPECT_TRUE(isModelNew());
diff --git a/src/lib/util/tests/stopwatch_unittest.cc b/src/lib/util/tests/stopwatch_unittest.cc
index c2fdb6c63b..d66254e3b7 100644
--- a/src/lib/util/tests/stopwatch_unittest.cc
+++ b/src/lib/util/tests/stopwatch_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -59,7 +59,7 @@ public:
protected:
- /// @brief Returs the current time.
+ /// @brief Returns the current time.
///
/// This method returns the fixed @c current_time_ timestamp.
virtual ptime getCurrentTime() const;
diff --git a/src/lib/util/tests/strutil_unittest.cc b/src/lib/util/tests/strutil_unittest.cc
index 887057d38e..7176e74c9d 100644
--- a/src/lib/util/tests/strutil_unittest.cc
+++ b/src/lib/util/tests/strutil_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -143,6 +143,42 @@ TEST(StringUtilTest, Tokens) {
EXPECT_EQ(string("gamma"), result[3]);
EXPECT_EQ(string("delta"), result[4]);
EXPECT_EQ(string("epsilon"), result[5]);
+
+ // Escaped delimiter
+ result = isc::util::str::tokens("foo\\,bar", ",", true);
+ EXPECT_EQ(1, result.size());
+ EXPECT_EQ(string("foo,bar"), result[0]);
+
+ // Escaped escape
+ result = isc::util::str::tokens("foo\\\\,bar", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("foo\\"), result[0]);
+ EXPECT_EQ(string("bar"), result[1]);
+
+ // Double escapes
+ result = isc::util::str::tokens("foo\\\\\\\\,\\bar", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("foo\\\\"), result[0]);
+ EXPECT_EQ(string("\\bar"), result[1]);
+
+ // Escaped standard character
+ result = isc::util::str::tokens("fo\\o,bar", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("fo\\o"), result[0]);
+ EXPECT_EQ(string("bar"), result[1]);
+
+ // Escape at the end
+ result = isc::util::str::tokens("foo,bar\\", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("foo"), result[0]);
+ EXPECT_EQ(string("bar\\"), result[1]);
+
+ // Escape opening a token
+ result = isc::util::str::tokens("foo,\\,,bar", ",", true);
+ ASSERT_EQ(3, result.size());
+ EXPECT_EQ(string("foo"), result[0]);
+ EXPECT_EQ(string(","), result[1]);
+ EXPECT_EQ(string("bar"), result[2]);
}
// Changing case
@@ -405,7 +441,7 @@ TEST(StringUtilTest, decodeFormattedHexString) {
testFormatted("", "");
std::vector<uint8_t> decoded;
- // Whitepspace.
+ // Whitespace.
EXPECT_THROW(decodeFormattedHexString("0a ", decoded),
isc::BadValue);
// Whitespace within a string.
diff --git a/src/lib/util/tests/versioned_csv_file_unittest.cc b/src/lib/util/tests/versioned_csv_file_unittest.cc
index 30b172e6ae..293c96bc2b 100644
--- a/src/lib/util/tests/versioned_csv_file_unittest.cc
+++ b/src/lib/util/tests/versioned_csv_file_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -148,7 +148,7 @@ TEST_F(VersionedCSVFileTest, addColumn) {
// We should have 3 defined columns
// Input Header should match defined columns on new files
// Valid columns should match defined columns on new files
- // Minium valid columns wasn't set. (Remember it's optional)
+ // Minimum valid columns wasn't set. (Remember it's optional)
EXPECT_EQ(3, csv->getColumnCount());
EXPECT_EQ(3, csv->getInputHeaderCount());
EXPECT_EQ(3, csv->getValidColumnCount());
@@ -193,7 +193,7 @@ TEST_F(VersionedCSVFileTest, currentSchemaTest) {
// 3 defined columns
// 3 columns total found in the header
// 3 valid columns found in the header
- // Minium valid columns wasn't set. (Remember it's optional)
+ // Minimum valid columns wasn't set. (Remember it's optional)
EXPECT_EQ(3, csv->getColumnCount());
EXPECT_EQ(3, csv->getInputHeaderCount());
EXPECT_EQ(3, csv->getValidColumnCount());
@@ -252,7 +252,7 @@ TEST_F(VersionedCSVFileTest, upgradeOlderVersions) {
// 2 defined columns
// 1 column found in the header
// 1 valid column in the header
- // Minium valid columns wasn't set. (Remember it's optional)
+ // Minimum valid columns wasn't set. (Remember it's optional)
EXPECT_EQ(2, csv->getColumnCount());
EXPECT_EQ(1, csv->getInputHeaderCount());
EXPECT_EQ(1, csv->getValidColumnCount());
@@ -312,7 +312,7 @@ TEST_F(VersionedCSVFileTest, upgradeOlderVersions) {
// 3 defined columns
// 1 column found in the header
// 1 valid column in the header
- // Minium valid columns wasn't set. (Remember it's optional)
+ // Minimum valid columns wasn't set. (Remember it's optional)
EXPECT_EQ(3, csv->getColumnCount());
EXPECT_EQ(1, csv->getInputHeaderCount());
EXPECT_EQ(1, csv->getValidColumnCount());
@@ -432,7 +432,7 @@ TEST_F(VersionedCSVFileTest, downGrading) {
// 2 defined columns
// 3 columns found in the header
// 2 valid columns in the header
- // Minium valid columns wasn't set. (Remember it's optional)
+ // Minimum valid columns wasn't set. (Remember it's optional)
EXPECT_EQ(2, csv->getColumnCount());
EXPECT_EQ(3, csv->getInputHeaderCount());
EXPECT_EQ(2, csv->getValidColumnCount());
diff --git a/src/lib/util/tests/watch_socket_unittests.cc b/src/lib/util/tests/watch_socket_unittests.cc
index fb8997a361..13ee4c16eb 100644
--- a/src/lib/util/tests/watch_socket_unittests.cc
+++ b/src/lib/util/tests/watch_socket_unittests.cc
@@ -213,7 +213,7 @@ TEST(WatchSocketTest, badReadOnClear) {
/// @todo maybe clear should never throw, log only
ASSERT_THROW(watch->clearReady(), WatchSocketError);
- // Verify the select_fd does not evalute to ready.
+ // Verify the select_fd does not evaluate to ready.
EXPECT_NE(1, selectCheck(select_fd));
// Verify that getSelectFd() returns INVALID.
diff --git a/src/lib/util/threads/tests/run_unittests.cc b/src/lib/util/threads/tests/run_unittests.cc
index d58abd7dbe..2e1b80164b 100644
--- a/src/lib/util/threads/tests/run_unittests.cc
+++ b/src/lib/util/threads/tests/run_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,7 +8,7 @@
#include <util/unittests/run_all.h>
#include <stdlib.h>
-// This file uses TEST_DATA_TOPBUILDDIR macro, which must point to a writeable
+// This file uses TEST_DATA_TOPBUILDDIR macro, which must point to a writable
// directory. It will be used for creating a logger lockfile.
int
diff --git a/src/lib/util/versioned_csv_file.h b/src/lib/util/versioned_csv_file.h
index 4967d346e8..cfd18d945c 100644
--- a/src/lib/util/versioned_csv_file.h
+++ b/src/lib/util/versioned_csv_file.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -171,7 +171,7 @@ public:
/// @brief Returns the number of valid columns found in the header
/// For newly created files this will always match the number of defined
/// columns (i.e. getColumnCount()). For existing files, this will be
- /// the number of columns in the header that match the defined columnns.
+ /// the number of columns in the header that match the defined columns.
/// When this number is less than getColumnCount() it means the input file
/// is from an earlier schema. This value is zero until the file has
/// been opened.
@@ -189,7 +189,7 @@ public:
/// true, the file will be opened and the internal pointer will be set
/// to the end of file.
///
- /// @param seek_to_end A boolean value which indicates if the intput and
+ /// @param seek_to_end A boolean value which indicates if the input and
/// output file pointer should be set at the end of file.
///
/// @throw VersionedCSVFileError if schema has not been defined,
diff --git a/src/lib/util/watch_socket.cc b/src/lib/util/watch_socket.cc
index fca762d905..a5b7b02887 100644
--- a/src/lib/util/watch_socket.cc
+++ b/src/lib/util/watch_socket.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -14,6 +14,7 @@
#include <sstream>
#include <string.h>
#include <sys/select.h>
+#include <unistd.h>
namespace isc {
namespace util {
@@ -131,7 +132,7 @@ WatchSocket::closeSocket(std::string& error_string) {
// destructors that throw.
if (source_ != SOCKET_NOT_VALID) {
if (close(source_)) {
- // An error occured.
+ // An error occurred.
s << "Could not close source: " << strerror(errno);
}
@@ -140,7 +141,7 @@ WatchSocket::closeSocket(std::string& error_string) {
if (sink_ != SOCKET_NOT_VALID) {
if (close(sink_)) {
- // An error occured.
+ // An error occurred.
if (error_string.empty()) {
s << "could not close sink: " << strerror(errno);
}