1#!/bin/sh
2
3# Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
4#
5# This Source Code Form is subject to the terms of the Mozilla Public
6# License, v. 2.0. If a copy of the MPL was not distributed with this
7# file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
9# shellcheck disable=SC1091
10# SC1091: Not following: ... was not specified as input (see shellcheck -x).
11
12# shellcheck disable=SC2154
13# SC2154: ... is referenced but not assigned.
14# Reason: some variables are sourced.
15
16# Exit with error if commands exit with non-zero and if undefined variables are
17# used.
18set -eu
19
20# Include common test library.
21. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
22
23# Include admin utilities
24. "@abs_top_srcdir@/src/bin/admin/admin-utils.sh"
25
26# Set path to the production schema scripts
27db_scripts_dir="@abs_top_srcdir@/src/share/database/scripts"
28
29# Set location of the kea-admin.
30kea_admin="@abs_top_builddir@/src/bin/admin/kea-admin"
31
32cql_wipe() {
33    # Wipe the database.
34    run_command \
35        cql_execute_script $db_scripts_dir/cql/dhcpdb_drop.cql
36    assert_eq 0 "${EXIT_CODE}" "drop table query failed, expected %d, returned %d"
37}
38
39cql_db_init_test() {
40    test_start "cql.init"
41
42    # Wipe the database.
43    cql_wipe
44
45    # Create the database
46    run_command \
47        "${kea_admin}" db-init cql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
48    assert_eq 0 "${EXIT_CODE}" "kea-admin db-init cql failed, expected exit code: %d, actual: %d"
49
50    # Verify that all the expected tables exist
51
52    # Check schema_version table
53    run_command \
54      cql_execute "SELECT version, minor FROM schema_version;"
55    assert_eq 0 "${EXIT_CODE}" "schema_version table check failed, expected exit code: %d, actual: %d"
56
57    # Check lease4 table
58    run_command \
59      cql_execute "SELECT address, hwaddr, client_id, valid_lifetime, expire, subnet_id, fqdn_fwd, fqdn_rev, hostname, state, user_context FROM lease4;"
60    assert_eq 0 "${EXIT_CODE}" "lease4 table check failed, expected exit code: %d, actual: %d"
61
62    # Check lease6 table
63    run_command \
64      cql_execute "SELECT address, duid, valid_lifetime, expire, subnet_id, pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, state, user_context FROM lease6;"
65    assert_eq 0 "${EXIT_CODE}" "lease6 table check failed, expected exit code: %d, actual: %d"
66
67    # Check lease6_types table
68    run_command \
69      cql_execute "SELECT lease_type, name FROM lease6_types;"
70    assert_eq 0 "${EXIT_CODE}" "lease6_types table check failed, expected exit code: %d, actual: %d"
71
72    # Check lease_state table
73    run_command \
74      cql_execute "SELECT state, name FROM lease_state;"
75    assert_eq 0 "${EXIT_CODE}" "lease_state table check failed, expected exit code: %d, actual: %d"
76
77    # Trying to create it again should fail. This verifies the db present
78    # check
79    echo "Making sure keyspace creation fails the second time..."
80    run_command \
81      "${kea_admin}" db-init cql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
82    assert_eq 2 "${EXIT_CODE}" "kea-admin failed to deny db-init, expected exit code: %d, actual: %d"
83
84    # Wipe the database.
85    cql_wipe
86
87    test_finish 0
88}
89
90cql_db_version_test() {
91    test_start "cql.version"
92
93    # Wipe the database.
94    cql_wipe
95
96    # Create the database.
97    run_command \
98      "${kea_admin}" db-init cql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
99    assert_eq 0 "${EXIT_CODE}" "kea-admin db-init cql failed, expected exit code: %d, actual: %d"
100
101    # Verify that kea-admin db-version returns the correct version.
102    version=$("${kea_admin}" db-version cql -u "${db_user}" -p "${db_password}" -n "${db_name}")
103    assert_str_eq "5.0" "${version}" "Expected kea-admin to return %s, returned value was %s"
104
105    # Wipe the database.
106    cql_wipe
107
108    test_finish 0
109}
110
111cql_upgrade_test() {
112    test_start "cql.upgrade"
113
114    # Wipe the database.
115    cql_wipe
116
117    # Initialize database to schema 1.0.
118    run_command \
119      cql_execute_script "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.cql"
120    assert_eq 0 "${EXIT_CODE}" "cannot initialize the database, expected exit code: %d, actual: %d"
121
122    run_command \
123      "${kea_admin}" db-upgrade cql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
124    assert_eq 0 "${EXIT_CODE}" "db-upgrade failed, expected exit code: %d, actual: %d"
125
126    # Wipe the database.
127    cql_wipe
128
129    test_finish 0
130}
131
132cql_lease4_dump_test() {
133    test_start "cql.lease4_dump_test"
134
135    test_dir="@abs_top_srcdir@/src/bin/admin/tests"
136    output_dir="@abs_top_builddir@/src/bin/admin/tests"
137
138    output_file="$output_dir/data/cql.lease4_dump_test.output.csv"
139    sorted_file="$output_dir/data/cql.lease4_dump_test.output.sorted.csv"
140    ref_file="$test_dir/data/cql.lease4_dump_test.reference.csv"
141
142    # Wipe out any residuals from prior failed runs.
143    if [ -e $output_file ]
144    then
145        rm $output_file
146    fi
147
148    if [ -e $sorted_file ]
149    then
150        rm $sorted_file
151    fi
152
153    # Wipe the database.
154    cql_wipe
155
156    # Create the database
157    run_command \
158        "${kea_admin}" db-init cql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
159    assert_eq 0 "${EXIT_CODE}" "kea-admin db-init cql failed, expected exit code: %d, actual: %d"
160
161    # Insert the reference record.
162    # -1073741302 corresponds to 192.0.2.10
163    # -1073741301 corresponds to 192.0.2.11
164    # -1073741300 corresponds to 192.0.2.12
165    # 1430694930 corresponds to 2015-04-04 01:15:30
166    # 1433464245 corresponds to 2015-05-05 02:30:45
167    # 1436173267 corresponds to 2015-06-06 11:01:07
168    insert_cql="\
169    INSERT INTO lease4 (address, hwaddr, client_id, valid_lifetime, expire, subnet_id, \
170        fqdn_fwd, fqdn_rev, hostname, state, user_context) \
171        VALUES (-1073741302,textAsBlob('20'),textAsBlob('30'),40,1430694930,50,true,true,'one.example.com', 0, '');\
172    INSERT INTO lease4 (address, hwaddr, client_id, valid_lifetime, expire, subnet_id, \
173        fqdn_fwd, fqdn_rev, hostname, state, user_context) \
174        VALUES (-1073741301,NULL,textAsBlob('123'),40,1433464245,50,true,true,'', 1, '');\
175    INSERT INTO lease4 (address, hwaddr, client_id, valid_lifetime, expire, subnet_id, \
176        fqdn_fwd, fqdn_rev, hostname, state, user_context) \
177        VALUES (-1073741300,textAsBlob('22'),NULL,40,1436173267,50,true,true,'three.example.com', 2, '');"
178
179    run_command \
180        cql_execute "$insert_cql"
181    assert_eq 0 "${EXIT_CODE}" "insert into lease4 failed, expected exit code %d, actual %d"
182
183    # Dump lease4 to output_file.
184    run_command \
185        "${kea_admin}" lease-dump cql -4 -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}" -o $output_file
186    assert_eq 0 "${EXIT_CODE}" "kea-admin lease-dump -4 failed, expected exit code %d, actual %d"
187
188    # sort data so we can compare
189    sort -g "${output_file}" > "${sorted_file}"
190
191    # Compare the dump output to reference file, they should be identical.
192    run_command \
193        cmp -s $sorted_file $ref_file
194    assert_eq 0 "${EXIT_CODE}" "dump file does not match reference file, expected exit code %d, actual %d"
195
196    # remove the output file.
197    rm $output_file
198
199    # remove the sorted file.
200    rm $sorted_file
201
202    # Wipe the database.
203    cql_wipe
204
205    test_finish 0
206}
207
208cql_lease6_dump_test() {
209    test_start "cql.lease6_dump_test"
210
211    test_dir="@abs_top_srcdir@/src/bin/admin/tests"
212    output_dir="@abs_top_builddir@/src/bin/admin/tests"
213
214    output_file="$output_dir/data/cql.lease6_dump_test.output.csv"
215    sorted_file="$output_dir/data/cql.lease6_dump_test.output.sorted.csv"
216    ref_file="$test_dir/data/cql.lease6_dump_test.reference.csv"
217
218    # Wipe out any residuals from prior failed runs.
219    if [ -e $output_file ]
220    then
221        rm $output_file
222    fi
223
224    if [ -e $sorted_file ]
225    then
226        rm $sorted_file
227    fi
228
229    # Wipe the database.
230    cql_wipe
231
232    # Create the database.
233    run_command \
234        "${kea_admin}" db-init cql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
235    assert_eq 0 "${EXIT_CODE}" "could not create database, expected exit code %d, actual %d"
236
237    # Insert the reference record.
238    # 1430694930 corresponds to 2015-04-04 01:15:30
239    # 1433464245 corresponds to 2015-05-05 02:30:45
240    # 1436173267 corresponds to 2015-06-06 11:01:07
241    insert_cql="\
242    INSERT INTO lease6 (address, duid, valid_lifetime, expire, subnet_id, \
243        pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, \
244        hwaddr, hwtype, hwaddr_source, state, user_context) \
245        VALUES ('2001:db8::10',textAsBlob('20'),30,1430694930,40,50,1,60,70,true,true, \
246            'one.example.com',textAsBlob('80'),90,16,0,'');\
247    INSERT INTO lease6 (address, duid, valid_lifetime, expire, subnet_id, \
248        pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, \
249        hwaddr, hwtype, hwaddr_source, state, user_context) \
250        VALUES ('2001:db8::11',NULL,30,1433464245,40,50,1,60,70,true,true, \
251            '',textAsBlob('80'),90,1,1,'');\
252    INSERT INTO lease6 (address, duid, valid_lifetime, expire, subnet_id, \
253        pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, \
254        hwaddr, hwtype, hwaddr_source, state, user_context) \
255        VALUES ('2001:db8::12',textAsBlob('21'),30,1436173267,40,50,1,60,70,true,true, \
256            'three.example.com',textAsBlob('80'),90,4,2,'');"
257
258    run_command \
259        cql_execute "$insert_cql"
260    assert_eq 0 "${EXIT_CODE}" "insert into lease6 failed, expected exit code %d, actual %d"
261
262    # Dump lease4 to output_file.
263    run_command \
264        "${kea_admin}" lease-dump cql -6 -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}" -o $output_file
265    assert_eq 0 "${EXIT_CODE}" "kea-admin lease-dump -6 failed, status code %d"
266
267    # sort data so we can compare
268    sort -g "${output_file}" > "${sorted_file}"
269
270    # Compare the dump output to reference file, they should be identical.
271    run_command \
272        cmp -s $sorted_file $ref_file
273    assert_eq 0 "${EXIT_CODE}" "dump file does not match reference file, expected exit code %d, actual %d"
274
275    # remove the output file.
276    rm $output_file
277
278    # remove the sorted file.
279    rm $sorted_file
280
281    # Wipe the database.
282    cql_wipe
283
284    test_finish 0
285}
286
287# Upgrades an existing schema to a target newer version
288# param target_version - desired schema version as "major.minor"
289cql_upgrade_schema_to_version() {
290    target_version=$1
291
292    # Check if the scripts directory exists at all.
293    if [ ! -d ${db_scripts_dir}/cql ]; then
294        log_error "Invalid scripts directory: ${db_scripts_dir}/cql"
295        exit 1
296    fi
297
298    # Check if there are any files in it
299    num_files=$(find ${db_scripts_dir}/cql/upgrade*.sh -type f | wc -l)
300    if [ "${num_files}" -eq 0 ]; then
301        log_error "No scripts in ${db_scripts_dir}/cql?"
302        exit 1
303    fi
304
305    version=$(cql_version)
306    for script in "${db_scripts_dir}"/cql/upgrade*.sh
307    do
308        if [ "${version}" = "${target_version}" ]
309        then
310            break
311        fi
312
313        echo "Processing $script file..."
314        "${script}" -u "${db_user}" -p "${db_password}" -k "${db_name}"
315        version=$(cql_version)
316    done
317
318    echo "Schema upgraded to $version"
319}
320
321# Verifies that you can upgrade from an earlier version and
322# that unused subnet ID values in hosts and options tables are
323# converted to -1
324cql_unused_subnet_id_test() {
325    test_start "cql.unused_subnet_id_test"
326
327    # Let's wipe the whole database
328    cql_wipe
329
330    # We need to create an older database with lease data so we can
331    # verify the upgrade mechanisms which convert subnet id values
332    #
333    # Initialize database to schema 1.0.
334    cql_execute_script "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.cql"
335
336    # Now upgrade to schema 2.0, the version just before global HRs
337    cql_upgrade_schema_to_version 2.0
338
339    # Now we need insert some hosts to "migrate" for both v4 and v6
340    qry="\
341    INSERT INTO host_reservations (id, host_identifier_type, host_identifier, host_ipv4_subnet_id, host_ipv6_subnet_id, hostname, option_subnet_id) \
342        VALUES (1, 0, textAsBlob('0123456'), 0, 0, 'host0', 0);\
343    INSERT INTO host_reservations (id, host_identifier_type, host_identifier, host_ipv4_subnet_id, host_ipv6_subnet_id, hostname, option_subnet_id) \
344        VALUES (2, 0, textAsBlob('1123456'), 4, 0, 'Host1', 4);\
345    INSERT INTO host_reservations (id, host_identifier_type, host_identifier, host_ipv4_subnet_id, host_ipv6_subnet_id, hostname, option_subnet_id) \
346        VALUES (3, 0, textAsBlob('2123456'), 0, 6, 'host2', 6);\
347    INSERT INTO host_reservations (id, host_identifier_type, host_identifier, host_ipv4_subnet_id, host_ipv6_subnet_id, hostname, option_subnet_id) \
348        VALUES (4, 0, textAsBlob('3123456'), 4, 6, 'Host3', 0);\
349    INSERT INTO host_reservations (id, host_identifier_type, host_identifier, host_ipv4_subnet_id, host_ipv6_subnet_id, hostname, option_subnet_id) \
350        VALUES (5, 0, textAsBlob('3123456'), -1, 6, 'host3', 6);"
351
352    run_command \
353        cql_execute "$qry"
354    assert_eq 0 "${EXIT_CODE}" "insert hosts failed, expected exit code: %d, actual: %d"
355
356    # Ok, we have a 2.0 database with hosts and options. Let's upgrade it.
357    run_command \
358      "${kea_admin}" db-upgrade cql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
359
360    # Upgrade should succeed
361    assert_eq 0 "${EXIT_CODE}" "upgrade failed"
362
363    test_dir="@abs_top_srcdir@/src/bin/admin/tests"
364    ref_file="$test_dir/data/cql.subnet_id_test.reference.csv"
365
366    output_dir="@abs_top_builddir@/src/bin/admin/tests"
367    export_file="$output_dir/data/cql.subnet_id_test.csv"
368    sorted_file="$output_dir/data/cql.subnet_id_test.sorted.csv"
369
370    # Fetch host_reservation data for comparison
371    echo "Exporting host_reservation data to $export_file ..."
372
373    qry="\
374    SELECT id, host_ipv4_subnet_id, host_ipv6_subnet_id, hostname, lower_case_hostname, option_subnet_id\
375        FROM hosts WHERE id IN (1,2,3,4,5) ALLOW FILTERING;"
376
377    run_command \
378        cql_execute "$qry"
379    printf '%s\n' "${OUTPUT}" > "${export_file}"
380    assert_eq 0 "${EXIT_CODE}" "insert hosts failed, expected exit code: %d, actual: %d"
381
382    # sort data so we can compare
383    grep -F '|' "${export_file}" | sort -V | tr -d " " | sed 's/|/,/g' > $sorted_file
384
385    # Compare the dump output to reference file, they should be identical.
386    run_command \
387        cmp -s $sorted_file $ref_file
388    assert_eq 0 "${EXIT_CODE}" "export file does not match reference file, expected exit code %d, actual %d"
389
390    # remove the output file.
391    rm $export_file
392
393    # remove the sorted file.
394    rm $sorted_file
395
396    # Wipe the database.
397    cql_wipe
398
399    # Report test success.
400    test_finish 0
401}
402
403# Verifies that you can upgrade from an earlier version and
404# that all hosts and options from old host_reservation table (version 3.0) are
405# converted to new schema (version 4.0) with new key partition key and are
406# moved to new hosts table
407cql_upgrade_hosts_test() {
408    test_start "cql.update_hosts_test"
409
410    # Let's wipe the whole database
411    cql_wipe
412
413    # We need to create an older database with lease data so we can
414    # verify the upgrade mechanisms which convert subnet id values
415    #
416    # Initialize database to schema 1.0.
417    cql_execute_script "@abs_top_srcdir@/src/bin/admin/tests/dhcpdb_create_1.0.cql"
418
419    # Now upgrade to schema 3.0, the version just before global HRs
420    cql_upgrade_schema_to_version 3.0
421
422    # Now we need insert some hosts to "migrate" for both v4 and v6
423    test_dir="@abs_top_srcdir@/src/bin/admin/tests"
424    data_file="$test_dir/data/cql.hosts_data_test.csv"
425    ref_file="$test_dir/data/cql.hosts_data_test.reference.csv"
426
427    output_dir="@abs_top_builddir@/src/bin/admin/tests"
428    export_file="$output_dir/data/cql.hosts_test.csv"
429    sorted_file="$output_dir/data/cql.hosts_test.sorted.csv"
430
431    # Fetch host_reservation data for comparison
432    echo "Exporting host_reservation data to $export_file ..."
433
434    qry="\
435    COPY host_reservations \
436        (id, host_identifier, host_identifier_type, host_ipv4_subnet_id, \
437         host_ipv6_subnet_id, host_ipv4_address, host_ipv4_next_server, \
438         host_ipv4_server_hostname, host_ipv4_boot_file_name, hostname, \
439         auth_key, user_context, host_ipv4_client_classes, \
440         host_ipv6_client_classes, reserved_ipv6_prefix_address, \
441         reserved_ipv6_prefix_length, reserved_ipv6_prefix_address_type, \
442         iaid, option_universe, option_code, option_value, \
443         option_formatted_value, option_space, option_is_persistent, \
444         option_client_class, option_subnet_id, option_user_context, \
445         option_scope_id) \
446    FROM '$data_file'"
447
448    run_command \
449        cql_execute "$qry"
450    assert_eq 0 "${EXIT_CODE}" "insert hosts failed, expected exit code: %d, actual: %d"
451
452    # Ok, we have a 3.0 database with hosts and options. Let's upgrade it.
453    run_command \
454      "${kea_admin}" db-upgrade cql -u "${db_user}" -p "${db_password}" -n "${db_name}" -d "${db_scripts_dir}"
455
456    # Upgrade should succeed
457    assert_eq 0 "${EXIT_CODE}" "upgrade failed"
458
459    qry="\
460    COPY hosts \
461        (id, key, host_identifier, host_identifier_type, host_ipv4_subnet_id, \
462         host_ipv6_subnet_id, host_ipv4_address, host_ipv4_next_server, \
463         host_ipv4_server_hostname, host_ipv4_boot_file_name, hostname, \
464         lower_case_hostname, auth_key, user_context, \
465         host_ipv4_client_classes, \
466         host_ipv6_client_classes, reserved_ipv6_prefix_address, \
467         reserved_ipv6_prefix_length, reserved_ipv6_prefix_address_type, \
468         iaid, option_universe, option_code, option_value, \
469         option_formatted_value, option_space, option_is_persistent, \
470         option_client_class, option_subnet_id, option_user_context, \
471         option_scope_id) \
472    TO '$export_file'"
473
474    run_command \
475        cql_execute "$qry"
476    assert_eq 0 "${EXIT_CODE}" "insert hosts failed, expected exit code: %d, actual: %d"
477
478    # sort data so we can compare
479    sort -V "${export_file}" > "${sorted_file}"
480
481    # Compare the dump output to reference file, they should be identical.
482    run_command \
483        cmp -s $sorted_file $ref_file
484    assert_eq 0 "${EXIT_CODE}" "export file does not match reference file, expected exit code %d, actual %d"
485
486    # remove the output file.
487    rm $export_file
488
489    # remove the sorted file.
490    rm $sorted_file
491
492    # Wipe the database.
493    cql_wipe
494
495    # Report test success.
496    test_finish 0
497}
498
499
500# Run tests.
501cql_db_init_test
502cql_db_version_test
503cql_upgrade_test
504cql_lease4_dump_test
505cql_lease6_dump_test
506cql_unused_subnet_id_test
507cql_upgrade_hosts_test
508