Browse Source

[master] Finished merge of trac5061 (database port and Cassandra fixes)

Francis Dupont 8 years ago
parent
commit
d12b6f4a6d

+ 7 - 1
ChangeLog

@@ -1,4 +1,10 @@
-1219.   [func]         marcin
+1220.	[func]		parisioa, fdupont, tomek
+	DHCPv4 and DHCPv6 parsers have updated to accept database port
+	parameter. The parameter for Cassandra is now called
+	"contact-points" (was "contact_points" previously).
+	(Trac #5061, git xxx)
+
+1219.   [func]		marcin
 	Control Agent uses libkea-http to process commands over
 	the RESTful interface.
         (Trac #5107, git 88ce715926a46b6b3832630116fc7782adc46c7b)

+ 2 - 0
doc/Makefile.am

@@ -13,6 +13,7 @@ nobase_dist_doc_DATA += examples/ddns/sample1.json
 nobase_dist_doc_DATA += examples/ddns/template.json
 nobase_dist_doc_DATA += examples/kea4/advanced.json
 nobase_dist_doc_DATA += examples/kea4/backends.json
+nobase_dist_doc_DATA += examples/kea4/cassandra.json
 nobase_dist_doc_DATA += examples/kea4/classify.json
 nobase_dist_doc_DATA += examples/kea4/dhcpv4-over-dhcpv6.json
 nobase_dist_doc_DATA += examples/kea4/hooks.json
@@ -26,6 +27,7 @@ nobase_dist_doc_DATA += examples/kea4/single-subnet.json
 nobase_dist_doc_DATA += examples/kea4/with-ddns.json
 nobase_dist_doc_DATA += examples/kea6/advanced.json
 nobase_dist_doc_DATA += examples/kea6/backends.json
+nobase_dist_doc_DATA += examples/kea6/cassandra.json
 nobase_dist_doc_DATA += examples/kea6/classify.json
 nobase_dist_doc_DATA += examples/kea6/dhcpv4-over-dhcpv6.json
 nobase_dist_doc_DATA += examples/kea6/duid.json

+ 8 - 3
doc/examples/kea4/backends.json

@@ -22,6 +22,7 @@
 # dependencies or services running.
 #  "lease-database": {
 #      "type": "memfile",
+#      "persist": true,
 #      "lfc-interval": 3600
 #  },
 
@@ -36,6 +37,7 @@
 #      "type": "mysql",
 #      "name": "keatest",
 #      "host": "localhost",
+#      "port": 3306,
 #      "user": "keatest",
 #      "password": "secret1",
 #      "connect-timeout": 3
@@ -52,21 +54,24 @@
 #      "type": "pgsql",
 #      "name": "keatest",
 #      "host": "localhost",
+#      "port": 5432,
 #      "user": "keatest",
-#      "password": "secret1"
+#      "password": "secret1",
+#      "connect-timeout": 3
 #  },
 
 # 4. CQL (Cassandra) backend. Leases will be stored in Cassandra database. Make
 # sure it is up, running and properly initialized. See kea-admin documentation
 # for details on how to initialize the database. The only strictly required
-# parameters are type, keyspace and contact_points. At least one contact point
+# parameters are type, keyspace and contact-points. At least one contact point
 # must be specified, but more than one is required for redundancy. Make sure
 # you specify the contact points without spaces. Kea must be compiled with
 # --with-cql option to use this backend.
 #  "lease-database": {
 #      "type": "cql",
 #      "keyspace": "keatest",
-#      "contact_points": "192.0.2.1,192.0.2.2,192.0.2.3"
+#      "contact-points": "192.0.2.1,192.0.2.2,192.0.2.3",
+#      "port": 9042
 #  },
 
 # Addresses will be assigned with a lifetime of 4000 seconds.

+ 64 - 0
doc/examples/kea4/cassandra.json

@@ -0,0 +1,64 @@
+// This is an example configuration file for the DHCPv4 server in Kea.
+// It is a basic scenario with one IPv4 subnet configured. It demonstrates
+// how to configure Kea to use CQL (Cassandra) backend
+
+{ "Dhcp4":
+
+{
+// Kea is told to listen on ethX interface only.
+  "interfaces-config": {
+    "interfaces": [ "ethX" ]
+  },
+
+// 4. CQL (Cassandra) backend. Leases will be stored in Cassandra database. Make
+// sure it is up, running and properly initialized. See kea-admin documentation
+// for details on how to initialize the database. The only strictly required
+// parameters are type, keyspace and contact-points. At least one contact point
+// must be specified, but more than one is required for redundancy. Make sure
+// you specify the contact points without spaces. Kea must be compiled with
+// --with-cql option to use this backend.
+  "lease-database": {
+      "type": "cql",
+      "keyspace": "keatest",
+      "contact-points": "192.0.2.1,192.0.2.2,192.0.2.3",
+      "port": 9042
+  },
+
+// Addresses will be assigned with a lifetime of 4000 seconds.
+  "valid-lifetime": 4000,
+
+// Renew and rebind timers are commented out. This implies that options
+// 58 and 59 will not be sent to the client. In this case it is up to
+// the client to pick the timer values according to RFC2131. Uncomment the
+// timers to send these options to the client.
+//  "renew-timer": 1000,
+//  "rebind-timer": 2000,
+
+// The following list defines subnets. We have only one subnet
+// here. We tell Kea that it is directly available over local interface.
+  "subnet4": [
+    {
+       "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
+       "subnet": "192.0.2.0/24",
+       "interface": "ethX"
+    }
+  ]
+},
+
+// The following configures logging. It assumes that messages with at least
+// informational level (info, warn, error and fatal) should be logged to stdout.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp4",
+            "output_options": [
+                {
+                    "output": "stdout"
+                }
+            ],
+            "severity": "INFO"
+        }
+    ]
+}
+
+}

+ 2 - 1
doc/examples/kea4/mysql-reservations.json

@@ -58,7 +58,8 @@
     "name": "kea",
     "user": "kea",
     "password": "kea",
-    "host": "localhost"
+    "host": "localhost",
+    "port": 3306
   },
 
 # Define a subnet with a single pool of dynamic addresses. Addresses from

+ 10 - 4
doc/examples/kea6/backends.json

@@ -21,7 +21,9 @@
 # This is the easiest backend to use as it does not require any extra
 # dependencies or services running.
   "lease-database": {
-      "type": "memfile"
+      "type": "memfile",
+      "persist": true,
+      "lfc-interval": 3600
   },
 
 # 2. MySQL backend. Leases will be stored in MySQL database. Make sure it
@@ -35,6 +37,7 @@
 #      "type": "mysql",
 #      "name": "keatest",
 #      "host": "localhost",
+#      "port": 3306,
 #      "user": "keatest",
 #      "password": "secret1",
 #      "connect-timeout": 3
@@ -51,21 +54,24 @@
 #      "type": "pgsql",
 #      "name": "keatest",
 #      "host": "localhost",
+#      "port": 5432,
 #      "user": "keatest",
-#      "password": "secret1"
+#      "password": "secret1",
+#      "connect-timeout": 3
 #  },
 
 # 4. CQL (Cassandra) backend. Leases will be stored in Cassandra database. Make
 # sure it is up, running and properly initialized. See kea-admin documentation
 # for details on how to initialize the database. The only strictly required
-# parameters are type, keyspace and contact_points. At least one contact point
+# parameters are type, keyspace and contact-points. At least one contact point
 # must be specified, but more than one is required for redundancy. Make sure
 # you specify the contact points without spaces. Kea must be compiled with
 # --with-cql option to use this backend.
 #  "lease-database": {
 #      "type": "cql",
 #      "keyspace": "keatest",
-#      "contact_points": "192.0.2.1,192.0.2.2,192.0.2.3"
+#      "contact-points": "192.0.2.1,192.0.2.2,192.0.2.3",
+#      "port": 9042
 #  },
 
 # Addresses will be assigned with preferred and valid lifetimes

+ 66 - 0
doc/examples/kea6/cassandra.json

@@ -0,0 +1,66 @@
+// This is an example configuration file for the DHCPv6 server in Kea.
+// It is a basic scenario with one IPv6 subnet configured. It demonstrates
+// how to configure Kea to use CQL (Cassandra) backend.
+
+{ "Dhcp6":
+
+{
+// Kea is told to listen on ethX interface only.
+  "interfaces-config": {
+    "interfaces": [ "ethX" ]
+  },
+
+// CQL (Cassandra) backend. Leases will be stored in Cassandra database. Make
+// sure it is up, running and properly initialized. See kea-admin documentation
+// for details on how to initialize the database. The only strictly required
+// parameters are type, keyspace and contact-points. At least one contact point
+// must be specified, but more than one is required for redundancy. Make sure
+// you specify the contact points without spaces. Kea must be compiled with
+// --with-cql option to use this backend.
+  "lease-database": {
+      "type": "cql",
+      "keyspace": "keatest",
+      "contact-points": "192.0.2.1,192.0.2.2,192.0.2.3",
+      "port": 9042
+  },
+
+// Addresses will be assigned with preferred and valid lifetimes
+// being 3000 and 4000, respectively. Client is told to start
+// renewing after 1000 seconds. If the server does not respond
+// after 2000 seconds since the lease was granted, client is supposed
+// to start REBIND procedure (emergency renewal that allows switching
+// to a different server).
+  "preferred-lifetime": 3000,
+  "valid-lifetime": 4000,
+  "renew-timer": 1000,
+  "rebind-timer": 2000,
+
+// The following list defines subnets. Each subnet consists of at
+// least subnet and pool entries.
+  "subnet6": [
+    {
+      "pools": [ { "pool": "2001:db8:1::/80" } ],
+      "subnet": "2001:db8:1::/64",
+      "interface": "ethX"
+    }
+  ]
+},
+
+// The following configures logging. It assumes that messages with at least
+// informational level (info, warn, error and fatal) should be logged to stdout.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp6",
+            "output_options": [
+                {
+                    "output": "stdout"
+                }
+            ],
+            "debuglevel": 0,
+            "severity": "INFO"
+        }
+    ]
+}
+
+}

+ 1 - 0
doc/examples/kea6/mysql-reservations.json

@@ -47,6 +47,7 @@
     "user": "kea",
     "password": "kea",
     "host": "localhost",
+    "port": 3306,
     "readonly": true
   },
 

+ 22 - 0
doc/guide/dhcp4-srv.xml

@@ -460,6 +460,11 @@ be followed by a comma and another object definition.</para>
 <screen>
 "Dhcp4": { "lease-database": { <userinput>"host" : ""</userinput>, ... }, ... }
 </screen>
+  Should the database use a port different than default, it may be
+  specified as well:
+<screen>
+"Dhcp4": { "lease-database": { <userinput>"port" : 12345</userinput>, ... }, ... }
+</screen>
   Should the database be located on a different system, you may need to specify a longer interval
   for the connection timeout:
 <screen>
@@ -468,6 +473,17 @@ be followed by a comma and another object definition.</para>
 The default value of five seconds should be more than adequate for local connections.
 If a timeout is given though, it should be an integer greater than zero.
   </para>
+
+  <para>
+    Note that host parameter is used by MySQL and PostgreSQL
+    backends. Cassandra has a concept of contact points that could be
+    used to contact the cluster, instead of a single IP or
+    hostname. It takes a list of comma separated IP addresses. This may be specified as:
+<screen>
+"Dhcp4": { "lease-database": { <userinput>"contact-points" : "192.0.2.1,192.0.2.2"</userinput>, ... }, ... }
+</screen>
+  </para>
+
   <para>Finally, the credentials of the account under which the server will
   access the database should be set:
 <screen>
@@ -527,6 +543,12 @@ If a timeout is given though, it should be an integer greater than zero.
 <screen>
 "Dhcp4": { "hosts-database": { <userinput>"host" : ""</userinput>, ... }, ... }
 </screen>
+  Should the database use a port different than default, it may be
+  specified as well:
+<screen>
+"Dhcp4": { "hosts-database": { <userinput>"port" : 12345</userinput>, ... }, ... }
+</screen>
+
   </para>
   <para>Finally, the credentials of the account under which the server will
   access the database should be set:

+ 19 - 0
doc/guide/dhcp6-srv.xml

@@ -455,6 +455,11 @@ be followed by a comma and another object definition.</para>
 <screen>
 "Dhcp6": { "lease-database": { <userinput>"host" : ""</userinput>, ... }, ... }
 </screen>
+  Should the database use a port different than default, it may be
+  specified as well:
+<screen>
+"Dhcp4": { "lease-database": { <userinput>"port" : 12345</userinput>, ... }, ... }
+</screen>
   Should the database be located on a different system, you may need to specify a longer interval
   for the connection timeout:
 <screen>
@@ -463,6 +468,17 @@ be followed by a comma and another object definition.</para>
 The default value of five seconds should be more than adequate for local connections.
 If a timeout is given though, it should be an integer greater than zero.
   </para>
+
+  <para>
+    Note that host parameter is used by MySQL and PostgreSQL
+    backends. Cassandra has a concept of contact points that could be
+    used to contact the cluster, instead of a single IP or
+    hostname. It takes a list of comma separated IP addresses. This may be specified as:
+<screen>
+"Dhcp4": { "lease-database": { <userinput>"contact-points" : "192.0.2.1,192.0.2.2"</userinput>, ... }, ... }
+</screen>
+  </para>
+
   <para>Finally, the credentials of the account under which the server will
   access the database should be set:
 <screen>
@@ -522,6 +538,9 @@ If a timeout is given though, it should be an integer greater than zero.
 <screen>
 "Dhcp6": { "hosts-database": { <userinput>"host" : ""</userinput>, ... }, ... }
 </screen>
+<screen>
+"Dhcp4": { "hosts-database": { <userinput>"port" : 12345</userinput>, ... }, ... }
+</screen>
   </para>
   <para>Finally, the credentials of the account under which the server will
   access the database should be set:

File diff suppressed because it is too large
+ 928 - 875
src/bin/dhcp4/dhcp4_lexer.cc


+ 30 - 0
src/bin/dhcp4/dhcp4_lexer.ll

@@ -330,6 +330,16 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 }
 
+\"port\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LEASE_DATABASE:
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp4Parser::make_PORT(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("port", driver.loc_);
+    }
+}
+
 \"persist\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::LEASE_DATABASE:
@@ -360,6 +370,26 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 }
 
+\"keyspace\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LEASE_DATABASE:
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp4Parser::make_KEYSPACE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("keyspace", driver.loc_);
+    }
+}
+
+\"contact-points\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::LEASE_DATABASE:
+    case isc::dhcp::Parser4Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp4Parser::make_CONTACT_POINTS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("contact-points", driver.loc_);
+    }
+}
+
 \"valid-lifetime\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:

File diff suppressed because it is too large
+ 1323 - 1266
src/bin/dhcp4/dhcp4_parser.cc


+ 183 - 149
src/bin/dhcp4/dhcp4_parser.h

@@ -374,108 +374,111 @@ namespace isc { namespace dhcp {
         TOKEN_USER = 283,
         TOKEN_PASSWORD = 284,
         TOKEN_HOST = 285,
-        TOKEN_PERSIST = 286,
-        TOKEN_LFC_INTERVAL = 287,
-        TOKEN_READONLY = 288,
-        TOKEN_CONNECT_TIMEOUT = 289,
-        TOKEN_VALID_LIFETIME = 290,
-        TOKEN_RENEW_TIMER = 291,
-        TOKEN_REBIND_TIMER = 292,
-        TOKEN_DECLINE_PROBATION_PERIOD = 293,
-        TOKEN_SUBNET4 = 294,
-        TOKEN_SUBNET_4O6_INTERFACE = 295,
-        TOKEN_SUBNET_4O6_INTERFACE_ID = 296,
-        TOKEN_SUBNET_4O6_SUBNET = 297,
-        TOKEN_OPTION_DEF = 298,
-        TOKEN_OPTION_DATA = 299,
-        TOKEN_NAME = 300,
-        TOKEN_DATA = 301,
-        TOKEN_CODE = 302,
-        TOKEN_SPACE = 303,
-        TOKEN_CSV_FORMAT = 304,
-        TOKEN_RECORD_TYPES = 305,
-        TOKEN_ENCAPSULATE = 306,
-        TOKEN_ARRAY = 307,
-        TOKEN_POOLS = 308,
-        TOKEN_POOL = 309,
-        TOKEN_USER_CONTEXT = 310,
-        TOKEN_SUBNET = 311,
-        TOKEN_INTERFACE = 312,
-        TOKEN_INTERFACE_ID = 313,
-        TOKEN_ID = 314,
-        TOKEN_RAPID_COMMIT = 315,
-        TOKEN_RESERVATION_MODE = 316,
-        TOKEN_HOST_RESERVATION_IDENTIFIERS = 317,
-        TOKEN_CLIENT_CLASSES = 318,
-        TOKEN_TEST = 319,
-        TOKEN_CLIENT_CLASS = 320,
-        TOKEN_RESERVATIONS = 321,
-        TOKEN_DUID = 322,
-        TOKEN_HW_ADDRESS = 323,
-        TOKEN_CIRCUIT_ID = 324,
-        TOKEN_CLIENT_ID = 325,
-        TOKEN_HOSTNAME = 326,
-        TOKEN_RELAY = 327,
-        TOKEN_IP_ADDRESS = 328,
-        TOKEN_HOOKS_LIBRARIES = 329,
-        TOKEN_LIBRARY = 330,
-        TOKEN_PARAMETERS = 331,
-        TOKEN_EXPIRED_LEASES_PROCESSING = 332,
-        TOKEN_RECLAIM_TIMER_WAIT_TIME = 333,
-        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 334,
-        TOKEN_HOLD_RECLAIMED_TIME = 335,
-        TOKEN_MAX_RECLAIM_LEASES = 336,
-        TOKEN_MAX_RECLAIM_TIME = 337,
-        TOKEN_UNWARNED_RECLAIM_CYCLES = 338,
-        TOKEN_DHCP4O6_PORT = 339,
-        TOKEN_CONTROL_SOCKET = 340,
-        TOKEN_SOCKET_TYPE = 341,
-        TOKEN_SOCKET_NAME = 342,
-        TOKEN_DHCP_DDNS = 343,
-        TOKEN_ENABLE_UPDATES = 344,
-        TOKEN_QUALIFYING_SUFFIX = 345,
-        TOKEN_SERVER_IP = 346,
-        TOKEN_SERVER_PORT = 347,
-        TOKEN_SENDER_IP = 348,
-        TOKEN_SENDER_PORT = 349,
-        TOKEN_MAX_QUEUE_SIZE = 350,
-        TOKEN_NCR_PROTOCOL = 351,
-        TOKEN_NCR_FORMAT = 352,
-        TOKEN_ALWAYS_INCLUDE_FQDN = 353,
-        TOKEN_ALLOW_CLIENT_UPDATE = 354,
-        TOKEN_OVERRIDE_NO_UPDATE = 355,
-        TOKEN_OVERRIDE_CLIENT_UPDATE = 356,
-        TOKEN_REPLACE_CLIENT_NAME = 357,
-        TOKEN_GENERATED_PREFIX = 358,
-        TOKEN_TCP = 359,
-        TOKEN_JSON = 360,
-        TOKEN_WHEN_PRESENT = 361,
-        TOKEN_NEVER = 362,
-        TOKEN_ALWAYS = 363,
-        TOKEN_WHEN_NOT_PRESENT = 364,
-        TOKEN_LOGGING = 365,
-        TOKEN_LOGGERS = 366,
-        TOKEN_OUTPUT_OPTIONS = 367,
-        TOKEN_OUTPUT = 368,
-        TOKEN_DEBUGLEVEL = 369,
-        TOKEN_SEVERITY = 370,
-        TOKEN_DHCP6 = 371,
-        TOKEN_DHCPDDNS = 372,
-        TOKEN_TOPLEVEL_JSON = 373,
-        TOKEN_TOPLEVEL_DHCP4 = 374,
-        TOKEN_SUB_DHCP4 = 375,
-        TOKEN_SUB_INTERFACES4 = 376,
-        TOKEN_SUB_SUBNET4 = 377,
-        TOKEN_SUB_POOL4 = 378,
-        TOKEN_SUB_RESERVATION = 379,
-        TOKEN_SUB_OPTION_DEF = 380,
-        TOKEN_SUB_OPTION_DATA = 381,
-        TOKEN_SUB_HOOKS_LIBRARY = 382,
-        TOKEN_SUB_DHCP_DDNS = 383,
-        TOKEN_STRING = 384,
-        TOKEN_INTEGER = 385,
-        TOKEN_FLOAT = 386,
-        TOKEN_BOOLEAN = 387
+        TOKEN_PORT = 286,
+        TOKEN_PERSIST = 287,
+        TOKEN_LFC_INTERVAL = 288,
+        TOKEN_READONLY = 289,
+        TOKEN_CONNECT_TIMEOUT = 290,
+        TOKEN_CONTACT_POINTS = 291,
+        TOKEN_KEYSPACE = 292,
+        TOKEN_VALID_LIFETIME = 293,
+        TOKEN_RENEW_TIMER = 294,
+        TOKEN_REBIND_TIMER = 295,
+        TOKEN_DECLINE_PROBATION_PERIOD = 296,
+        TOKEN_SUBNET4 = 297,
+        TOKEN_SUBNET_4O6_INTERFACE = 298,
+        TOKEN_SUBNET_4O6_INTERFACE_ID = 299,
+        TOKEN_SUBNET_4O6_SUBNET = 300,
+        TOKEN_OPTION_DEF = 301,
+        TOKEN_OPTION_DATA = 302,
+        TOKEN_NAME = 303,
+        TOKEN_DATA = 304,
+        TOKEN_CODE = 305,
+        TOKEN_SPACE = 306,
+        TOKEN_CSV_FORMAT = 307,
+        TOKEN_RECORD_TYPES = 308,
+        TOKEN_ENCAPSULATE = 309,
+        TOKEN_ARRAY = 310,
+        TOKEN_POOLS = 311,
+        TOKEN_POOL = 312,
+        TOKEN_USER_CONTEXT = 313,
+        TOKEN_SUBNET = 314,
+        TOKEN_INTERFACE = 315,
+        TOKEN_INTERFACE_ID = 316,
+        TOKEN_ID = 317,
+        TOKEN_RAPID_COMMIT = 318,
+        TOKEN_RESERVATION_MODE = 319,
+        TOKEN_HOST_RESERVATION_IDENTIFIERS = 320,
+        TOKEN_CLIENT_CLASSES = 321,
+        TOKEN_TEST = 322,
+        TOKEN_CLIENT_CLASS = 323,
+        TOKEN_RESERVATIONS = 324,
+        TOKEN_DUID = 325,
+        TOKEN_HW_ADDRESS = 326,
+        TOKEN_CIRCUIT_ID = 327,
+        TOKEN_CLIENT_ID = 328,
+        TOKEN_HOSTNAME = 329,
+        TOKEN_RELAY = 330,
+        TOKEN_IP_ADDRESS = 331,
+        TOKEN_HOOKS_LIBRARIES = 332,
+        TOKEN_LIBRARY = 333,
+        TOKEN_PARAMETERS = 334,
+        TOKEN_EXPIRED_LEASES_PROCESSING = 335,
+        TOKEN_RECLAIM_TIMER_WAIT_TIME = 336,
+        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 337,
+        TOKEN_HOLD_RECLAIMED_TIME = 338,
+        TOKEN_MAX_RECLAIM_LEASES = 339,
+        TOKEN_MAX_RECLAIM_TIME = 340,
+        TOKEN_UNWARNED_RECLAIM_CYCLES = 341,
+        TOKEN_DHCP4O6_PORT = 342,
+        TOKEN_CONTROL_SOCKET = 343,
+        TOKEN_SOCKET_TYPE = 344,
+        TOKEN_SOCKET_NAME = 345,
+        TOKEN_DHCP_DDNS = 346,
+        TOKEN_ENABLE_UPDATES = 347,
+        TOKEN_QUALIFYING_SUFFIX = 348,
+        TOKEN_SERVER_IP = 349,
+        TOKEN_SERVER_PORT = 350,
+        TOKEN_SENDER_IP = 351,
+        TOKEN_SENDER_PORT = 352,
+        TOKEN_MAX_QUEUE_SIZE = 353,
+        TOKEN_NCR_PROTOCOL = 354,
+        TOKEN_NCR_FORMAT = 355,
+        TOKEN_ALWAYS_INCLUDE_FQDN = 356,
+        TOKEN_ALLOW_CLIENT_UPDATE = 357,
+        TOKEN_OVERRIDE_NO_UPDATE = 358,
+        TOKEN_OVERRIDE_CLIENT_UPDATE = 359,
+        TOKEN_REPLACE_CLIENT_NAME = 360,
+        TOKEN_GENERATED_PREFIX = 361,
+        TOKEN_TCP = 362,
+        TOKEN_JSON = 363,
+        TOKEN_WHEN_PRESENT = 364,
+        TOKEN_NEVER = 365,
+        TOKEN_ALWAYS = 366,
+        TOKEN_WHEN_NOT_PRESENT = 367,
+        TOKEN_LOGGING = 368,
+        TOKEN_LOGGERS = 369,
+        TOKEN_OUTPUT_OPTIONS = 370,
+        TOKEN_OUTPUT = 371,
+        TOKEN_DEBUGLEVEL = 372,
+        TOKEN_SEVERITY = 373,
+        TOKEN_DHCP6 = 374,
+        TOKEN_DHCPDDNS = 375,
+        TOKEN_TOPLEVEL_JSON = 376,
+        TOKEN_TOPLEVEL_DHCP4 = 377,
+        TOKEN_SUB_DHCP4 = 378,
+        TOKEN_SUB_INTERFACES4 = 379,
+        TOKEN_SUB_SUBNET4 = 380,
+        TOKEN_SUB_POOL4 = 381,
+        TOKEN_SUB_RESERVATION = 382,
+        TOKEN_SUB_OPTION_DEF = 383,
+        TOKEN_SUB_OPTION_DATA = 384,
+        TOKEN_SUB_HOOKS_LIBRARY = 385,
+        TOKEN_SUB_DHCP_DDNS = 386,
+        TOKEN_STRING = 387,
+        TOKEN_INTEGER = 388,
+        TOKEN_FLOAT = 389,
+        TOKEN_BOOLEAN = 390
       };
     };
 
@@ -708,6 +711,10 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_PORT (const location_type& l);
+
+    static inline
+    symbol_type
     make_PERSIST (const location_type& l);
 
     static inline
@@ -724,6 +731,14 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_CONTACT_POINTS (const location_type& l);
+
+    static inline
+    symbol_type
+    make_KEYSPACE (const location_type& l);
+
+    static inline
+    symbol_type
     make_VALID_LIFETIME (const location_type& l);
 
     static inline
@@ -1319,12 +1334,12 @@ namespace isc { namespace dhcp {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 719,     ///< Last index in yytable_.
-      yynnts_ = 302,  ///< Number of nonterminal symbols.
+      yylast_ = 733,     ///< Last index in yytable_.
+      yynnts_ = 307,  ///< Number of nonterminal symbols.
       yyfinal_ = 24, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
-      yyntokens_ = 133  ///< Number of tokens.
+      yyntokens_ = 136  ///< Number of tokens.
     };
 
 
@@ -1379,9 +1394,10 @@ namespace isc { namespace dhcp {
       95,    96,    97,    98,    99,   100,   101,   102,   103,   104,
      105,   106,   107,   108,   109,   110,   111,   112,   113,   114,
      115,   116,   117,   118,   119,   120,   121,   122,   123,   124,
-     125,   126,   127,   128,   129,   130,   131,   132
+     125,   126,   127,   128,   129,   130,   131,   132,   133,   134,
+     135
     };
-    const unsigned int user_token_number_max_ = 387;
+    const unsigned int user_token_number_max_ = 390;
     const token_number_type undef_token_ = 2;
 
     if (static_cast<int>(t) <= yyeof_)
@@ -1414,28 +1430,28 @@ namespace isc { namespace dhcp {
   {
       switch (other.type_get ())
     {
-      case 146: // value
-      case 150: // map_value
-      case 188: // socket_type
-      case 197: // db_type
-      case 397: // ncr_protocol_value
-      case 406: // replace_client_name_value
+      case 149: // value
+      case 153: // map_value
+      case 191: // socket_type
+      case 200: // db_type
+      case 405: // ncr_protocol_value
+      case 414: // replace_client_name_value
         value.copy< ElementPtr > (other.value);
         break;
 
-      case 132: // "boolean"
+      case 135: // "boolean"
         value.copy< bool > (other.value);
         break;
 
-      case 131: // "floating point"
+      case 134: // "floating point"
         value.copy< double > (other.value);
         break;
 
-      case 130: // "integer"
+      case 133: // "integer"
         value.copy< int64_t > (other.value);
         break;
 
-      case 129: // "constant string"
+      case 132: // "constant string"
         value.copy< std::string > (other.value);
         break;
 
@@ -1456,28 +1472,28 @@ namespace isc { namespace dhcp {
     (void) v;
       switch (this->type_get ())
     {
-      case 146: // value
-      case 150: // map_value
-      case 188: // socket_type
-      case 197: // db_type
-      case 397: // ncr_protocol_value
-      case 406: // replace_client_name_value
+      case 149: // value
+      case 153: // map_value
+      case 191: // socket_type
+      case 200: // db_type
+      case 405: // ncr_protocol_value
+      case 414: // replace_client_name_value
         value.copy< ElementPtr > (v);
         break;
 
-      case 132: // "boolean"
+      case 135: // "boolean"
         value.copy< bool > (v);
         break;
 
-      case 131: // "floating point"
+      case 134: // "floating point"
         value.copy< double > (v);
         break;
 
-      case 130: // "integer"
+      case 133: // "integer"
         value.copy< int64_t > (v);
         break;
 
-      case 129: // "constant string"
+      case 132: // "constant string"
         value.copy< std::string > (v);
         break;
 
@@ -1557,28 +1573,28 @@ namespace isc { namespace dhcp {
     // Type destructor.
     switch (yytype)
     {
-      case 146: // value
-      case 150: // map_value
-      case 188: // socket_type
-      case 197: // db_type
-      case 397: // ncr_protocol_value
-      case 406: // replace_client_name_value
+      case 149: // value
+      case 153: // map_value
+      case 191: // socket_type
+      case 200: // db_type
+      case 405: // ncr_protocol_value
+      case 414: // replace_client_name_value
         value.template destroy< ElementPtr > ();
         break;
 
-      case 132: // "boolean"
+      case 135: // "boolean"
         value.template destroy< bool > ();
         break;
 
-      case 131: // "floating point"
+      case 134: // "floating point"
         value.template destroy< double > ();
         break;
 
-      case 130: // "integer"
+      case 133: // "integer"
         value.template destroy< int64_t > ();
         break;
 
-      case 129: // "constant string"
+      case 132: // "constant string"
         value.template destroy< std::string > ();
         break;
 
@@ -1605,28 +1621,28 @@ namespace isc { namespace dhcp {
     super_type::move(s);
       switch (this->type_get ())
     {
-      case 146: // value
-      case 150: // map_value
-      case 188: // socket_type
-      case 197: // db_type
-      case 397: // ncr_protocol_value
-      case 406: // replace_client_name_value
+      case 149: // value
+      case 153: // map_value
+      case 191: // socket_type
+      case 200: // db_type
+      case 405: // ncr_protocol_value
+      case 414: // replace_client_name_value
         value.move< ElementPtr > (s.value);
         break;
 
-      case 132: // "boolean"
+      case 135: // "boolean"
         value.move< bool > (s.value);
         break;
 
-      case 131: // "floating point"
+      case 134: // "floating point"
         value.move< double > (s.value);
         break;
 
-      case 130: // "integer"
+      case 133: // "integer"
         value.move< int64_t > (s.value);
         break;
 
-      case 129: // "constant string"
+      case 132: // "constant string"
         value.move< std::string > (s.value);
         break;
 
@@ -1698,7 +1714,7 @@ namespace isc { namespace dhcp {
      355,   356,   357,   358,   359,   360,   361,   362,   363,   364,
      365,   366,   367,   368,   369,   370,   371,   372,   373,   374,
      375,   376,   377,   378,   379,   380,   381,   382,   383,   384,
-     385,   386,   387
+     385,   386,   387,   388,   389,   390
     };
     return static_cast<token_type> (yytoken_number_[type]);
   }
@@ -1878,6 +1894,12 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp4Parser::symbol_type
+  Dhcp4Parser::make_PORT (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_PORT, l);
+  }
+
+  Dhcp4Parser::symbol_type
   Dhcp4Parser::make_PERSIST (const location_type& l)
   {
     return symbol_type (token::TOKEN_PERSIST, l);
@@ -1902,6 +1924,18 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp4Parser::symbol_type
+  Dhcp4Parser::make_CONTACT_POINTS (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_CONTACT_POINTS, l);
+  }
+
+  Dhcp4Parser::symbol_type
+  Dhcp4Parser::make_KEYSPACE (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_KEYSPACE, l);
+  }
+
+  Dhcp4Parser::symbol_type
   Dhcp4Parser::make_VALID_LIFETIME (const location_type& l)
   {
     return symbol_type (token::TOKEN_VALID_LIFETIME, l);
@@ -2492,7 +2526,7 @@ namespace isc { namespace dhcp {
 
 #line 14 "dhcp4_parser.yy" // lalr1.cc:377
 } } // isc::dhcp
-#line 2496 "dhcp4_parser.h" // lalr1.cc:377
+#line 2530 "dhcp4_parser.h" // lalr1.cc:377
 
 
 

+ 28 - 0
src/bin/dhcp4/dhcp4_parser.yy

@@ -72,10 +72,13 @@ using namespace std;
   USER "user"
   PASSWORD "password"
   HOST "host"
+  PORT "port"
   PERSIST "persist"
   LFC_INTERVAL "lfc-interval"
   READONLY "readonly"
   CONNECT_TIMEOUT "connect-timeout"
+  CONTACT_POINTS "contact-points"
+  KEYSPACE "keyspace"
 
   VALID_LIFETIME "valid-lifetime"
   RENEW_TIMER "renew-timer"
@@ -513,11 +516,14 @@ database_map_param: database_type
                   | user
                   | password
                   | host
+                  | port
                   | name
                   | persist
                   | lfc_interval
                   | readonly
                   | connect_timeout
+                  | contact_points
+                  | keyspace
                   | unknown_map_entry
 ;
 
@@ -558,6 +564,11 @@ host: HOST {
     ctx.leave();
 };
 
+port: PORT COLON INTEGER {
+    ElementPtr p(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("port", p);
+};
+
 name: NAME {
     ctx.enter(ctx.NO_KEYWORD);
 } COLON STRING {
@@ -586,6 +597,23 @@ connect_timeout: CONNECT_TIMEOUT COLON INTEGER {
     ctx.stack_.back()->set("connect-timeout", n);
 };
 
+contact_points: CONTACT_POINTS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr cp(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("contact-points", cp);
+    ctx.leave();
+};
+
+keyspace: KEYSPACE {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr ks(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("keyspace", ks);
+    ctx.leave();
+};
+
+
 host_reservation_identifiers: HOST_RESERVATION_IDENTIFIERS {
     ElementPtr l(new ListElement(ctx.loc2pos(@1)));
     ctx.stack_.back()->set("host-reservation-identifiers", l);

+ 1 - 0
src/bin/dhcp4/tests/parser_unittest.cc

@@ -243,6 +243,7 @@ TEST(ParserTest, file) {
     vector<string> configs = { "advanced.json" ,
                                "backends.json",
                                "classify.json",
+                               "cassandra.json",
                                "dhcpv4-over-dhcpv6.json",
                                "hooks.json",
                                "leases-expiration.json",

File diff suppressed because it is too large
+ 907 - 856
src/bin/dhcp6/dhcp6_lexer.cc


+ 30 - 0
src/bin/dhcp6/dhcp6_lexer.ll

@@ -530,6 +530,16 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 }
 
+\"port\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser6Context::LEASE_DATABASE:
+    case isc::dhcp::Parser6Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp6Parser::make_PORT(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp6Parser::make_STRING("port", driver.loc_);
+    }
+}
+
 \"persist\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::LEASE_DATABASE:
@@ -561,6 +571,26 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 }
 
+\"keyspace\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser6Context::LEASE_DATABASE:
+    case isc::dhcp::Parser6Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp6Parser::make_KEYSPACE(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp6Parser::make_STRING("keyspace", driver.loc_);
+    }
+}
+
+\"contact-points\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser6Context::LEASE_DATABASE:
+    case isc::dhcp::Parser6Context::HOSTS_DATABASE:
+        return isc::dhcp::Dhcp6Parser::make_CONTACT_POINTS(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp6Parser::make_STRING("contact-points", driver.loc_);
+    }
+}
+
 \"preferred-lifetime\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:

File diff suppressed because it is too large
+ 1408 - 1346
src/bin/dhcp6/dhcp6_parser.cc


+ 198 - 165
src/bin/dhcp6/dhcp6_parser.h

@@ -366,124 +366,127 @@ namespace isc { namespace dhcp {
         TOKEN_USER = 275,
         TOKEN_PASSWORD = 276,
         TOKEN_HOST = 277,
-        TOKEN_PERSIST = 278,
-        TOKEN_LFC_INTERVAL = 279,
-        TOKEN_READONLY = 280,
-        TOKEN_CONNECT_TIMEOUT = 281,
-        TOKEN_PREFERRED_LIFETIME = 282,
-        TOKEN_VALID_LIFETIME = 283,
-        TOKEN_RENEW_TIMER = 284,
-        TOKEN_REBIND_TIMER = 285,
-        TOKEN_DECLINE_PROBATION_PERIOD = 286,
-        TOKEN_SUBNET6 = 287,
-        TOKEN_OPTION_DEF = 288,
-        TOKEN_OPTION_DATA = 289,
-        TOKEN_NAME = 290,
-        TOKEN_DATA = 291,
-        TOKEN_CODE = 292,
-        TOKEN_SPACE = 293,
-        TOKEN_CSV_FORMAT = 294,
-        TOKEN_RECORD_TYPES = 295,
-        TOKEN_ENCAPSULATE = 296,
-        TOKEN_ARRAY = 297,
-        TOKEN_POOLS = 298,
-        TOKEN_POOL = 299,
-        TOKEN_PD_POOLS = 300,
-        TOKEN_PREFIX = 301,
-        TOKEN_PREFIX_LEN = 302,
-        TOKEN_EXCLUDED_PREFIX = 303,
-        TOKEN_EXCLUDED_PREFIX_LEN = 304,
-        TOKEN_DELEGATED_LEN = 305,
-        TOKEN_USER_CONTEXT = 306,
-        TOKEN_SUBNET = 307,
-        TOKEN_INTERFACE = 308,
-        TOKEN_INTERFACE_ID = 309,
-        TOKEN_ID = 310,
-        TOKEN_RAPID_COMMIT = 311,
-        TOKEN_RESERVATION_MODE = 312,
-        TOKEN_MAC_SOURCES = 313,
-        TOKEN_RELAY_SUPPLIED_OPTIONS = 314,
-        TOKEN_HOST_RESERVATION_IDENTIFIERS = 315,
-        TOKEN_CLIENT_CLASSES = 316,
-        TOKEN_TEST = 317,
-        TOKEN_CLIENT_CLASS = 318,
-        TOKEN_RESERVATIONS = 319,
-        TOKEN_IP_ADDRESSES = 320,
-        TOKEN_PREFIXES = 321,
-        TOKEN_DUID = 322,
-        TOKEN_HW_ADDRESS = 323,
-        TOKEN_HOSTNAME = 324,
-        TOKEN_RELAY = 325,
-        TOKEN_IP_ADDRESS = 326,
-        TOKEN_HOOKS_LIBRARIES = 327,
-        TOKEN_LIBRARY = 328,
-        TOKEN_PARAMETERS = 329,
-        TOKEN_EXPIRED_LEASES_PROCESSING = 330,
-        TOKEN_RECLAIM_TIMER_WAIT_TIME = 331,
-        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 332,
-        TOKEN_HOLD_RECLAIMED_TIME = 333,
-        TOKEN_MAX_RECLAIM_LEASES = 334,
-        TOKEN_MAX_RECLAIM_TIME = 335,
-        TOKEN_UNWARNED_RECLAIM_CYCLES = 336,
-        TOKEN_SERVER_ID = 337,
-        TOKEN_LLT = 338,
-        TOKEN_EN = 339,
-        TOKEN_LL = 340,
-        TOKEN_IDENTIFIER = 341,
-        TOKEN_HTYPE = 342,
-        TOKEN_TIME = 343,
-        TOKEN_ENTERPRISE_ID = 344,
-        TOKEN_DHCP4O6_PORT = 345,
-        TOKEN_CONTROL_SOCKET = 346,
-        TOKEN_SOCKET_TYPE = 347,
-        TOKEN_SOCKET_NAME = 348,
-        TOKEN_DHCP_DDNS = 349,
-        TOKEN_ENABLE_UPDATES = 350,
-        TOKEN_QUALIFYING_SUFFIX = 351,
-        TOKEN_SERVER_IP = 352,
-        TOKEN_SERVER_PORT = 353,
-        TOKEN_SENDER_IP = 354,
-        TOKEN_SENDER_PORT = 355,
-        TOKEN_MAX_QUEUE_SIZE = 356,
-        TOKEN_NCR_PROTOCOL = 357,
-        TOKEN_NCR_FORMAT = 358,
-        TOKEN_ALWAYS_INCLUDE_FQDN = 359,
-        TOKEN_ALLOW_CLIENT_UPDATE = 360,
-        TOKEN_OVERRIDE_NO_UPDATE = 361,
-        TOKEN_OVERRIDE_CLIENT_UPDATE = 362,
-        TOKEN_REPLACE_CLIENT_NAME = 363,
-        TOKEN_GENERATED_PREFIX = 364,
-        TOKEN_UDP = 365,
-        TOKEN_TCP = 366,
-        TOKEN_JSON = 367,
-        TOKEN_WHEN_PRESENT = 368,
-        TOKEN_NEVER = 369,
-        TOKEN_ALWAYS = 370,
-        TOKEN_WHEN_NOT_PRESENT = 371,
-        TOKEN_LOGGING = 372,
-        TOKEN_LOGGERS = 373,
-        TOKEN_OUTPUT_OPTIONS = 374,
-        TOKEN_OUTPUT = 375,
-        TOKEN_DEBUGLEVEL = 376,
-        TOKEN_SEVERITY = 377,
-        TOKEN_DHCP4 = 378,
-        TOKEN_DHCPDDNS = 379,
-        TOKEN_TOPLEVEL_JSON = 380,
-        TOKEN_TOPLEVEL_DHCP6 = 381,
-        TOKEN_SUB_DHCP6 = 382,
-        TOKEN_SUB_INTERFACES6 = 383,
-        TOKEN_SUB_SUBNET6 = 384,
-        TOKEN_SUB_POOL6 = 385,
-        TOKEN_SUB_PD_POOL = 386,
-        TOKEN_SUB_RESERVATION = 387,
-        TOKEN_SUB_OPTION_DEF = 388,
-        TOKEN_SUB_OPTION_DATA = 389,
-        TOKEN_SUB_HOOKS_LIBRARY = 390,
-        TOKEN_SUB_DHCP_DDNS = 391,
-        TOKEN_STRING = 392,
-        TOKEN_INTEGER = 393,
-        TOKEN_FLOAT = 394,
-        TOKEN_BOOLEAN = 395
+        TOKEN_PORT = 278,
+        TOKEN_PERSIST = 279,
+        TOKEN_LFC_INTERVAL = 280,
+        TOKEN_READONLY = 281,
+        TOKEN_CONNECT_TIMEOUT = 282,
+        TOKEN_CONTACT_POINTS = 283,
+        TOKEN_KEYSPACE = 284,
+        TOKEN_PREFERRED_LIFETIME = 285,
+        TOKEN_VALID_LIFETIME = 286,
+        TOKEN_RENEW_TIMER = 287,
+        TOKEN_REBIND_TIMER = 288,
+        TOKEN_DECLINE_PROBATION_PERIOD = 289,
+        TOKEN_SUBNET6 = 290,
+        TOKEN_OPTION_DEF = 291,
+        TOKEN_OPTION_DATA = 292,
+        TOKEN_NAME = 293,
+        TOKEN_DATA = 294,
+        TOKEN_CODE = 295,
+        TOKEN_SPACE = 296,
+        TOKEN_CSV_FORMAT = 297,
+        TOKEN_RECORD_TYPES = 298,
+        TOKEN_ENCAPSULATE = 299,
+        TOKEN_ARRAY = 300,
+        TOKEN_POOLS = 301,
+        TOKEN_POOL = 302,
+        TOKEN_PD_POOLS = 303,
+        TOKEN_PREFIX = 304,
+        TOKEN_PREFIX_LEN = 305,
+        TOKEN_EXCLUDED_PREFIX = 306,
+        TOKEN_EXCLUDED_PREFIX_LEN = 307,
+        TOKEN_DELEGATED_LEN = 308,
+        TOKEN_USER_CONTEXT = 309,
+        TOKEN_SUBNET = 310,
+        TOKEN_INTERFACE = 311,
+        TOKEN_INTERFACE_ID = 312,
+        TOKEN_ID = 313,
+        TOKEN_RAPID_COMMIT = 314,
+        TOKEN_RESERVATION_MODE = 315,
+        TOKEN_MAC_SOURCES = 316,
+        TOKEN_RELAY_SUPPLIED_OPTIONS = 317,
+        TOKEN_HOST_RESERVATION_IDENTIFIERS = 318,
+        TOKEN_CLIENT_CLASSES = 319,
+        TOKEN_TEST = 320,
+        TOKEN_CLIENT_CLASS = 321,
+        TOKEN_RESERVATIONS = 322,
+        TOKEN_IP_ADDRESSES = 323,
+        TOKEN_PREFIXES = 324,
+        TOKEN_DUID = 325,
+        TOKEN_HW_ADDRESS = 326,
+        TOKEN_HOSTNAME = 327,
+        TOKEN_RELAY = 328,
+        TOKEN_IP_ADDRESS = 329,
+        TOKEN_HOOKS_LIBRARIES = 330,
+        TOKEN_LIBRARY = 331,
+        TOKEN_PARAMETERS = 332,
+        TOKEN_EXPIRED_LEASES_PROCESSING = 333,
+        TOKEN_RECLAIM_TIMER_WAIT_TIME = 334,
+        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 335,
+        TOKEN_HOLD_RECLAIMED_TIME = 336,
+        TOKEN_MAX_RECLAIM_LEASES = 337,
+        TOKEN_MAX_RECLAIM_TIME = 338,
+        TOKEN_UNWARNED_RECLAIM_CYCLES = 339,
+        TOKEN_SERVER_ID = 340,
+        TOKEN_LLT = 341,
+        TOKEN_EN = 342,
+        TOKEN_LL = 343,
+        TOKEN_IDENTIFIER = 344,
+        TOKEN_HTYPE = 345,
+        TOKEN_TIME = 346,
+        TOKEN_ENTERPRISE_ID = 347,
+        TOKEN_DHCP4O6_PORT = 348,
+        TOKEN_CONTROL_SOCKET = 349,
+        TOKEN_SOCKET_TYPE = 350,
+        TOKEN_SOCKET_NAME = 351,
+        TOKEN_DHCP_DDNS = 352,
+        TOKEN_ENABLE_UPDATES = 353,
+        TOKEN_QUALIFYING_SUFFIX = 354,
+        TOKEN_SERVER_IP = 355,
+        TOKEN_SERVER_PORT = 356,
+        TOKEN_SENDER_IP = 357,
+        TOKEN_SENDER_PORT = 358,
+        TOKEN_MAX_QUEUE_SIZE = 359,
+        TOKEN_NCR_PROTOCOL = 360,
+        TOKEN_NCR_FORMAT = 361,
+        TOKEN_ALWAYS_INCLUDE_FQDN = 362,
+        TOKEN_ALLOW_CLIENT_UPDATE = 363,
+        TOKEN_OVERRIDE_NO_UPDATE = 364,
+        TOKEN_OVERRIDE_CLIENT_UPDATE = 365,
+        TOKEN_REPLACE_CLIENT_NAME = 366,
+        TOKEN_GENERATED_PREFIX = 367,
+        TOKEN_UDP = 368,
+        TOKEN_TCP = 369,
+        TOKEN_JSON = 370,
+        TOKEN_WHEN_PRESENT = 371,
+        TOKEN_NEVER = 372,
+        TOKEN_ALWAYS = 373,
+        TOKEN_WHEN_NOT_PRESENT = 374,
+        TOKEN_LOGGING = 375,
+        TOKEN_LOGGERS = 376,
+        TOKEN_OUTPUT_OPTIONS = 377,
+        TOKEN_OUTPUT = 378,
+        TOKEN_DEBUGLEVEL = 379,
+        TOKEN_SEVERITY = 380,
+        TOKEN_DHCP4 = 381,
+        TOKEN_DHCPDDNS = 382,
+        TOKEN_TOPLEVEL_JSON = 383,
+        TOKEN_TOPLEVEL_DHCP6 = 384,
+        TOKEN_SUB_DHCP6 = 385,
+        TOKEN_SUB_INTERFACES6 = 386,
+        TOKEN_SUB_SUBNET6 = 387,
+        TOKEN_SUB_POOL6 = 388,
+        TOKEN_SUB_PD_POOL = 389,
+        TOKEN_SUB_RESERVATION = 390,
+        TOKEN_SUB_OPTION_DEF = 391,
+        TOKEN_SUB_OPTION_DATA = 392,
+        TOKEN_SUB_HOOKS_LIBRARY = 393,
+        TOKEN_SUB_DHCP_DDNS = 394,
+        TOKEN_STRING = 395,
+        TOKEN_INTEGER = 396,
+        TOKEN_FLOAT = 397,
+        TOKEN_BOOLEAN = 398
       };
     };
 
@@ -684,6 +687,10 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_PORT (const location_type& l);
+
+    static inline
+    symbol_type
     make_PERSIST (const location_type& l);
 
     static inline
@@ -700,6 +707,14 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_CONTACT_POINTS (const location_type& l);
+
+    static inline
+    symbol_type
+    make_KEYSPACE (const location_type& l);
+
+    static inline
+    symbol_type
     make_PREFERRED_LIFETIME (const location_type& l);
 
     static inline
@@ -1359,12 +1374,12 @@ namespace isc { namespace dhcp {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 767,     ///< Last index in yytable_.
-      yynnts_ = 317,  ///< Number of nonterminal symbols.
+      yylast_ = 781,     ///< Last index in yytable_.
+      yynnts_ = 322,  ///< Number of nonterminal symbols.
       yyfinal_ = 26, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
-      yyntokens_ = 141  ///< Number of tokens.
+      yyntokens_ = 144  ///< Number of tokens.
     };
 
 
@@ -1420,9 +1435,9 @@ namespace isc { namespace dhcp {
      105,   106,   107,   108,   109,   110,   111,   112,   113,   114,
      115,   116,   117,   118,   119,   120,   121,   122,   123,   124,
      125,   126,   127,   128,   129,   130,   131,   132,   133,   134,
-     135,   136,   137,   138,   139,   140
+     135,   136,   137,   138,   139,   140,   141,   142,   143
     };
-    const unsigned int user_token_number_max_ = 395;
+    const unsigned int user_token_number_max_ = 398;
     const token_number_type undef_token_ = 2;
 
     if (static_cast<int>(t) <= yyeof_)
@@ -1455,28 +1470,28 @@ namespace isc { namespace dhcp {
   {
       switch (other.type_get ())
     {
-      case 155: // value
-      case 159: // map_value
-      case 200: // db_type
-      case 387: // duid_type
-      case 420: // ncr_protocol_value
-      case 429: // replace_client_name_value
+      case 158: // value
+      case 162: // map_value
+      case 203: // db_type
+      case 395: // duid_type
+      case 428: // ncr_protocol_value
+      case 437: // replace_client_name_value
         value.copy< ElementPtr > (other.value);
         break;
 
-      case 140: // "boolean"
+      case 143: // "boolean"
         value.copy< bool > (other.value);
         break;
 
-      case 139: // "floating point"
+      case 142: // "floating point"
         value.copy< double > (other.value);
         break;
 
-      case 138: // "integer"
+      case 141: // "integer"
         value.copy< int64_t > (other.value);
         break;
 
-      case 137: // "constant string"
+      case 140: // "constant string"
         value.copy< std::string > (other.value);
         break;
 
@@ -1497,28 +1512,28 @@ namespace isc { namespace dhcp {
     (void) v;
       switch (this->type_get ())
     {
-      case 155: // value
-      case 159: // map_value
-      case 200: // db_type
-      case 387: // duid_type
-      case 420: // ncr_protocol_value
-      case 429: // replace_client_name_value
+      case 158: // value
+      case 162: // map_value
+      case 203: // db_type
+      case 395: // duid_type
+      case 428: // ncr_protocol_value
+      case 437: // replace_client_name_value
         value.copy< ElementPtr > (v);
         break;
 
-      case 140: // "boolean"
+      case 143: // "boolean"
         value.copy< bool > (v);
         break;
 
-      case 139: // "floating point"
+      case 142: // "floating point"
         value.copy< double > (v);
         break;
 
-      case 138: // "integer"
+      case 141: // "integer"
         value.copy< int64_t > (v);
         break;
 
-      case 137: // "constant string"
+      case 140: // "constant string"
         value.copy< std::string > (v);
         break;
 
@@ -1598,28 +1613,28 @@ namespace isc { namespace dhcp {
     // Type destructor.
     switch (yytype)
     {
-      case 155: // value
-      case 159: // map_value
-      case 200: // db_type
-      case 387: // duid_type
-      case 420: // ncr_protocol_value
-      case 429: // replace_client_name_value
+      case 158: // value
+      case 162: // map_value
+      case 203: // db_type
+      case 395: // duid_type
+      case 428: // ncr_protocol_value
+      case 437: // replace_client_name_value
         value.template destroy< ElementPtr > ();
         break;
 
-      case 140: // "boolean"
+      case 143: // "boolean"
         value.template destroy< bool > ();
         break;
 
-      case 139: // "floating point"
+      case 142: // "floating point"
         value.template destroy< double > ();
         break;
 
-      case 138: // "integer"
+      case 141: // "integer"
         value.template destroy< int64_t > ();
         break;
 
-      case 137: // "constant string"
+      case 140: // "constant string"
         value.template destroy< std::string > ();
         break;
 
@@ -1646,28 +1661,28 @@ namespace isc { namespace dhcp {
     super_type::move(s);
       switch (this->type_get ())
     {
-      case 155: // value
-      case 159: // map_value
-      case 200: // db_type
-      case 387: // duid_type
-      case 420: // ncr_protocol_value
-      case 429: // replace_client_name_value
+      case 158: // value
+      case 162: // map_value
+      case 203: // db_type
+      case 395: // duid_type
+      case 428: // ncr_protocol_value
+      case 437: // replace_client_name_value
         value.move< ElementPtr > (s.value);
         break;
 
-      case 140: // "boolean"
+      case 143: // "boolean"
         value.move< bool > (s.value);
         break;
 
-      case 139: // "floating point"
+      case 142: // "floating point"
         value.move< double > (s.value);
         break;
 
-      case 138: // "integer"
+      case 141: // "integer"
         value.move< int64_t > (s.value);
         break;
 
-      case 137: // "constant string"
+      case 140: // "constant string"
         value.move< std::string > (s.value);
         break;
 
@@ -1740,7 +1755,7 @@ namespace isc { namespace dhcp {
      365,   366,   367,   368,   369,   370,   371,   372,   373,   374,
      375,   376,   377,   378,   379,   380,   381,   382,   383,   384,
      385,   386,   387,   388,   389,   390,   391,   392,   393,   394,
-     395
+     395,   396,   397,   398
     };
     return static_cast<token_type> (yytoken_number_[type]);
   }
@@ -1872,6 +1887,12 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp6Parser::symbol_type
+  Dhcp6Parser::make_PORT (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_PORT, l);
+  }
+
+  Dhcp6Parser::symbol_type
   Dhcp6Parser::make_PERSIST (const location_type& l)
   {
     return symbol_type (token::TOKEN_PERSIST, l);
@@ -1896,6 +1917,18 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp6Parser::symbol_type
+  Dhcp6Parser::make_CONTACT_POINTS (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_CONTACT_POINTS, l);
+  }
+
+  Dhcp6Parser::symbol_type
+  Dhcp6Parser::make_KEYSPACE (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_KEYSPACE, l);
+  }
+
+  Dhcp6Parser::symbol_type
   Dhcp6Parser::make_PREFERRED_LIFETIME (const location_type& l)
   {
     return symbol_type (token::TOKEN_PREFERRED_LIFETIME, l);
@@ -2582,7 +2615,7 @@ namespace isc { namespace dhcp {
 
 #line 14 "dhcp6_parser.yy" // lalr1.cc:377
 } } // isc::dhcp
-#line 2586 "dhcp6_parser.h" // lalr1.cc:377
+#line 2619 "dhcp6_parser.h" // lalr1.cc:377
 
 
 

+ 28 - 0
src/bin/dhcp6/dhcp6_parser.yy

@@ -63,10 +63,13 @@ using namespace std;
   USER "user"
   PASSWORD "password"
   HOST "host"
+  PORT "port"
   PERSIST "persist"
   LFC_INTERVAL "lfc-interval"
   READONLY "readonly"
   CONNECT_TIMEOUT "connect-timeout"
+  CONTACT_POINTS "contact-points"
+  KEYSPACE "keyspace"
 
   PREFERRED_LIFETIME "preferred-lifetime"
   VALID_LIFETIME "valid-lifetime"
@@ -498,11 +501,14 @@ database_map_param: database_type
                   | user
                   | password
                   | host
+                  | port
                   | name
                   | persist
                   | lfc_interval
                   | readonly
                   | connect_timeout
+                  | contact_points
+                  | keyspace
                   | unknown_map_entry
 ;
 
@@ -543,6 +549,11 @@ host: HOST {
     ctx.leave();
 };
 
+port: PORT COLON INTEGER {
+    ElementPtr p(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("port", p);
+};
+
 name: NAME {
     ctx.enter(ctx.NO_KEYWORD);
 } COLON STRING {
@@ -571,6 +582,23 @@ connect_timeout: CONNECT_TIMEOUT COLON INTEGER {
     ctx.stack_.back()->set("connect-timeout", n);
 };
 
+contact_points: CONTACT_POINTS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr cp(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("contact-points", cp);
+    ctx.leave();
+};
+
+keyspace: KEYSPACE {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr ks(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("keyspace", ks);
+    ctx.leave();
+};
+
+
 mac_sources: MAC_SOURCES {
     ElementPtr l(new ListElement(ctx.loc2pos(@1)));
     ctx.stack_.back()->set("mac-sources", l);

+ 1 - 1
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".

+ 1 - 1
src/lib/dhcpsrv/database_backends.dox

@@ -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.

+ 28 - 1
src/lib/dhcpsrv/mysql_connection.cc

@@ -66,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 {
@@ -173,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_));
     }

+ 16 - 0
src/lib/dhcpsrv/parsers/dbaccess_parser.cc

@@ -55,6 +55,7 @@ DbAccessParser::parse(CfgDbAccessPtr& cfg_db,
 
     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, database_config->mapValue()) {
         try {
@@ -72,6 +73,11 @@ DbAccessParser::parse(CfgDbAccessPtr& cfg_db,
                 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();
             }
@@ -128,6 +134,16 @@ DbAccessParser::parse(CfgDbAccessPtr& cfg_db,
                   << " (" << 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
     // swapping contents - values_copy is destroyed immediately after the
     // operation (when the method exits), so we are not interested in its new

+ 4 - 5
src/lib/dhcpsrv/parsers/dbaccess_parser.h

@@ -57,6 +57,7 @@ public:
     /// - "type" is "memfile", "mysql" or "postgresql"
     /// - "lfc-interval" 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.
@@ -65,10 +66,9 @@ public:
     /// @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.
+    /// @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);
 
@@ -94,7 +94,6 @@ protected:
     /// @return Database access string
     std::string getDbAccessString() const;
 
-
 private:
 
     std::map<std::string, std::string> values_; ///< Stored parameter values

+ 35 - 1
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
@@ -153,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");

+ 57 - 1
src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc

@@ -178,6 +178,7 @@ private:
      bool quoteValue(const std::string& parameter) const {
          return ((parameter != "persist") && (parameter != "lfc-interval") &&
                  (parameter != "connect-timeout") &&
+                 (parameter != "port") &&
                  (parameter != "readonly"));
     }
 
@@ -402,10 +403,61 @@ TEST_F(DbAccessParserTest, largeTimeout) {
     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",
@@ -423,6 +475,7 @@ TEST_F(DbAccessParserTest, validTypeMysql) {
 // 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",
@@ -445,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",
@@ -456,6 +510,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
                                   NULL};
     const char* config3[] = {"type",     "mysql",
                              "host",     "erewhon",
+                             "port",     "3306",
                              "user",     "me",
                              "password", "meagain",
                              "name",     "keatest",
@@ -475,6 +530,7 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
                                   NULL};
     const char* config4[] = {"type",     "mysql",
                              "host",     "erewhon",
+                             "port",     "3306",
                              "user",     "them",
                              "password", "",
                              "name",     "keatest",
@@ -536,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};