summaryrefslogtreecommitdiffstats
path: root/src/share
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
commit040eee1aa49b49df4698d83a05af57c220127fd1 (patch)
treef635435954e6ccde5eee9893889e24f30ca68346 /src/share
parentInitial commit. (diff)
downloadisc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.tar.xz
isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.zip
Adding upstream version 2.2.0.upstream/2.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/share')
-rw-r--r--src/share/Makefile.am1
-rw-r--r--src/share/Makefile.in704
-rw-r--r--src/share/api/Makefile.am17
-rw-r--r--src/share/api/Makefile.in726
-rw-r--r--src/share/api/README27
-rw-r--r--src/share/api/_template.json48
-rw-r--r--src/share/api/api_files.mk190
-rw-r--r--src/share/api/build-report.json28
-rw-r--r--src/share/api/cache-clear.json13
-rw-r--r--src/share/api/cache-flush.json19
-rw-r--r--src/share/api/cache-get-by-id.json28
-rw-r--r--src/share/api/cache-get.json20
-rw-r--r--src/share/api/cache-insert.json53
-rw-r--r--src/share/api/cache-load.json20
-rw-r--r--src/share/api/cache-remove.json32
-rw-r--r--src/share/api/cache-size.json20
-rw-r--r--src/share/api/cache-write.json22
-rw-r--r--src/share/api/class-add.json42
-rw-r--r--src/share/api/class-del.json31
-rw-r--r--src/share/api/class-get.json45
-rw-r--r--src/share/api/class-list.json48
-rw-r--r--src/share/api/class-update.json42
-rw-r--r--src/share/api/config-backend-pull.json31
-rw-r--r--src/share/api/config-get.json30
-rw-r--r--src/share/api/config-reload.json20
-rw-r--r--src/share/api/config-set.json34
-rw-r--r--src/share/api/config-test.json34
-rw-r--r--src/share/api/config-write.json24
-rw-r--r--src/share/api/dhcp-disable.json22
-rw-r--r--src/share/api/dhcp-enable.json21
-rwxr-xr-xsrc/share/api/generate-api-files15
-rwxr-xr-xsrc/share/api/generate-templates43
-rw-r--r--src/share/api/gss-tsig-get-all.json53
-rw-r--r--src/share/api/gss-tsig-get.json44
-rw-r--r--src/share/api/gss-tsig-key-del.json23
-rw-r--r--src/share/api/gss-tsig-key-expire.json23
-rw-r--r--src/share/api/gss-tsig-key-get.json33
-rw-r--r--src/share/api/gss-tsig-list.json31
-rw-r--r--src/share/api/gss-tsig-purge-all.json20
-rw-r--r--src/share/api/gss-tsig-purge.json24
-rw-r--r--src/share/api/gss-tsig-rekey-all.json20
-rw-r--r--src/share/api/gss-tsig-rekey.json24
-rw-r--r--src/share/api/ha-continue.json14
-rw-r--r--src/share/api/ha-heartbeat.json34
-rw-r--r--src/share/api/ha-maintenance-cancel.json17
-rw-r--r--src/share/api/ha-maintenance-notify.json37
-rw-r--r--src/share/api/ha-maintenance-start.json19
-rw-r--r--src/share/api/ha-reset.json14
-rw-r--r--src/share/api/ha-scopes.json26
-rw-r--r--src/share/api/ha-sync-complete-notify.json19
-rw-r--r--src/share/api/ha-sync.json24
-rw-r--r--src/share/api/lease4-add.json25
-rw-r--r--src/share/api/lease4-del.json24
-rw-r--r--src/share/api/lease4-get-all.json66
-rw-r--r--src/share/api/lease4-get-by-client-id.json49
-rw-r--r--src/share/api/lease4-get-by-hostname.json49
-rw-r--r--src/share/api/lease4-get-by-hw-address.json50
-rw-r--r--src/share/api/lease4-get-page.json65
-rw-r--r--src/share/api/lease4-get.json44
-rw-r--r--src/share/api/lease4-resend-ddns.json32
-rw-r--r--src/share/api/lease4-update.json25
-rw-r--r--src/share/api/lease4-wipe.json21
-rw-r--r--src/share/api/lease6-add.json32
-rw-r--r--src/share/api/lease6-bulk-apply.json75
-rw-r--r--src/share/api/lease6-del.json24
-rw-r--r--src/share/api/lease6-get-all.json71
-rw-r--r--src/share/api/lease6-get-by-duid.json52
-rw-r--r--src/share/api/lease6-get-by-hostname.json52
-rw-r--r--src/share/api/lease6-get-page.json70
-rw-r--r--src/share/api/lease6-get.json56
-rw-r--r--src/share/api/lease6-resend-ddns.json32
-rw-r--r--src/share/api/lease6-update.json27
-rw-r--r--src/share/api/lease6-wipe.json24
-rw-r--r--src/share/api/leases-reclaim.json21
-rw-r--r--src/share/api/libreload.json22
-rw-r--r--src/share/api/list-commands.json24
-rw-r--r--src/share/api/network4-add.json55
-rw-r--r--src/share/api/network4-del.json35
-rw-r--r--src/share/api/network4-get.json57
-rw-r--r--src/share/api/network4-list.json30
-rw-r--r--src/share/api/network4-subnet-add.json28
-rw-r--r--src/share/api/network4-subnet-del.json28
-rw-r--r--src/share/api/network6-add.json55
-rw-r--r--src/share/api/network6-del.json35
-rw-r--r--src/share/api/network6-get.json57
-rw-r--r--src/share/api/network6-list.json30
-rw-r--r--src/share/api/network6-subnet-add.json28
-rw-r--r--src/share/api/network6-subnet-del.json28
-rw-r--r--src/share/api/remote-class4-del.json39
-rw-r--r--src/share/api/remote-class4-get-all.json52
-rw-r--r--src/share/api/remote-class4-get.json51
-rw-r--r--src/share/api/remote-class4-set.json44
-rw-r--r--src/share/api/remote-class6-del.json39
-rw-r--r--src/share/api/remote-class6-get-all.json52
-rw-r--r--src/share/api/remote-class6-get.json51
-rw-r--r--src/share/api/remote-class6-set.json44
-rw-r--r--src/share/api/remote-global-parameter4-del.json36
-rw-r--r--src/share/api/remote-global-parameter4-get-all.json52
-rw-r--r--src/share/api/remote-global-parameter4-get.json45
-rw-r--r--src/share/api/remote-global-parameter4-set.json43
-rw-r--r--src/share/api/remote-global-parameter6-del.json36
-rw-r--r--src/share/api/remote-global-parameter6-get-all.json52
-rw-r--r--src/share/api/remote-global-parameter6-get.json45
-rw-r--r--src/share/api/remote-global-parameter6-set.json43
-rw-r--r--src/share/api/remote-network4-del.json40
-rw-r--r--src/share/api/remote-network4-get.json52
-rw-r--r--src/share/api/remote-network4-list.json52
-rw-r--r--src/share/api/remote-network4-set.json37
-rw-r--r--src/share/api/remote-network6-del.json40
-rw-r--r--src/share/api/remote-network6-get.json52
-rw-r--r--src/share/api/remote-network6-list.json52
-rw-r--r--src/share/api/remote-network6-set.json37
-rw-r--r--src/share/api/remote-option-def4-del.json39
-rw-r--r--src/share/api/remote-option-def4-get-all.json52
-rw-r--r--src/share/api/remote-option-def4-get.json52
-rw-r--r--src/share/api/remote-option-def4-set.json37
-rw-r--r--src/share/api/remote-option-def6-del.json39
-rw-r--r--src/share/api/remote-option-def6-get-all.json52
-rw-r--r--src/share/api/remote-option-def6-get.json52
-rw-r--r--src/share/api/remote-option-def6-set.json37
-rw-r--r--src/share/api/remote-option4-global-del.json41
-rw-r--r--src/share/api/remote-option4-global-get-all.json52
-rw-r--r--src/share/api/remote-option4-global-get.json51
-rw-r--r--src/share/api/remote-option4-global-set.json45
-rw-r--r--src/share/api/remote-option4-network-del.json45
-rw-r--r--src/share/api/remote-option4-network-set.json49
-rw-r--r--src/share/api/remote-option4-pool-del.json45
-rw-r--r--src/share/api/remote-option4-pool-set.json49
-rw-r--r--src/share/api/remote-option4-subnet-del.json45
-rw-r--r--src/share/api/remote-option4-subnet-set.json49
-rw-r--r--src/share/api/remote-option6-global-del.json41
-rw-r--r--src/share/api/remote-option6-global-get-all.json52
-rw-r--r--src/share/api/remote-option6-global-get.json51
-rw-r--r--src/share/api/remote-option6-global-set.json45
-rw-r--r--src/share/api/remote-option6-network-del.json45
-rw-r--r--src/share/api/remote-option6-network-set.json49
-rw-r--r--src/share/api/remote-option6-pd-pool-del.json46
-rw-r--r--src/share/api/remote-option6-pd-pool-set.json50
-rw-r--r--src/share/api/remote-option6-pool-del.json45
-rw-r--r--src/share/api/remote-option6-pool-set.json49
-rw-r--r--src/share/api/remote-option6-subnet-del.json45
-rw-r--r--src/share/api/remote-option6-subnet-set.json49
-rw-r--r--src/share/api/remote-server4-del.json39
-rw-r--r--src/share/api/remote-server4-get-all.json47
-rw-r--r--src/share/api/remote-server4-get.json48
-rw-r--r--src/share/api/remote-server4-set.json45
-rw-r--r--src/share/api/remote-server6-del.json39
-rw-r--r--src/share/api/remote-server6-get-all.json47
-rw-r--r--src/share/api/remote-server6-get.json48
-rw-r--r--src/share/api/remote-server6-set.json45
-rw-r--r--src/share/api/remote-subnet4-del-by-id.json39
-rw-r--r--src/share/api/remote-subnet4-del-by-prefix.json39
-rw-r--r--src/share/api/remote-subnet4-get-by-id.json49
-rw-r--r--src/share/api/remote-subnet4-get-by-prefix.json51
-rw-r--r--src/share/api/remote-subnet4-list.json56
-rw-r--r--src/share/api/remote-subnet4-set.json44
-rw-r--r--src/share/api/remote-subnet6-del-by-id.json39
-rw-r--r--src/share/api/remote-subnet6-del-by-prefix.json39
-rw-r--r--src/share/api/remote-subnet6-get-by-id.json53
-rw-r--r--src/share/api/remote-subnet6-get-by-prefix.json51
-rw-r--r--src/share/api/remote-subnet6-list.json56
-rw-r--r--src/share/api/remote-subnet6-set.json44
-rw-r--r--src/share/api/reservation-add.json50
-rw-r--r--src/share/api/reservation-del.json27
-rw-r--r--src/share/api/reservation-get-all.json24
-rw-r--r--src/share/api/reservation-get-by-hostname.json25
-rw-r--r--src/share/api/reservation-get-by-id.json25
-rw-r--r--src/share/api/reservation-get-page.json27
-rw-r--r--src/share/api/reservation-get.json54
-rw-r--r--src/share/api/server-tag-get.json30
-rw-r--r--src/share/api/shutdown.json26
-rw-r--r--src/share/api/stat-lease4-get.json38
-rw-r--r--src/share/api/stat-lease6-get.json38
-rw-r--r--src/share/api/statistic-get-all.json64
-rw-r--r--src/share/api/statistic-get.json33
-rw-r--r--src/share/api/statistic-remove-all.json22
-rw-r--r--src/share/api/statistic-remove.json24
-rw-r--r--src/share/api/statistic-reset-all.json23
-rw-r--r--src/share/api/statistic-reset.json25
-rw-r--r--src/share/api/statistic-sample-age-set-all.json24
-rw-r--r--src/share/api/statistic-sample-age-set.json25
-rw-r--r--src/share/api/statistic-sample-count-set-all.json24
-rw-r--r--src/share/api/statistic-sample-count-set.json25
-rw-r--r--src/share/api/status-get.json64
-rw-r--r--src/share/api/subnet4-add.json39
-rw-r--r--src/share/api/subnet4-del.json35
-rw-r--r--src/share/api/subnet4-delta-add.json39
-rw-r--r--src/share/api/subnet4-delta-del.json39
-rw-r--r--src/share/api/subnet4-get.json39
-rw-r--r--src/share/api/subnet4-list.json39
-rw-r--r--src/share/api/subnet4-update.json39
-rw-r--r--src/share/api/subnet6-add.json39
-rw-r--r--src/share/api/subnet6-del.json33
-rw-r--r--src/share/api/subnet6-delta-add.json39
-rw-r--r--src/share/api/subnet6-delta-del.json39
-rw-r--r--src/share/api/subnet6-get.json39
-rw-r--r--src/share/api/subnet6-list.json39
-rw-r--r--src/share/api/subnet6-update.json39
-rw-r--r--src/share/api/version-get.json20
-rw-r--r--src/share/database/Makefile.am1
-rw-r--r--src/share/database/Makefile.in704
-rw-r--r--src/share/database/scripts/Makefile.am1
-rw-r--r--src/share/database/scripts/Makefile.in704
-rw-r--r--src/share/database/scripts/mysql/Makefile.am40
-rw-r--r--src/share/database/scripts/mysql/Makefile.in904
-rw-r--r--src/share/database/scripts/mysql/dhcpdb_create.mysql5094
-rw-r--r--src/share/database/scripts/mysql/dhcpdb_drop.mysql153
-rw-r--r--src/share/database/scripts/mysql/upgrade_001.0_to_002.0.sh.in57
-rw-r--r--src/share/database/scripts/mysql/upgrade_002.0_to_003.0.sh.in118
-rw-r--r--src/share/database/scripts/mysql/upgrade_003.0_to_004.0.sh.in173
-rw-r--r--src/share/database/scripts/mysql/upgrade_004.0_to_004.1.sh.in104
-rw-r--r--src/share/database/scripts/mysql/upgrade_004.1_to_005.0.sh.in128
-rw-r--r--src/share/database/scripts/mysql/upgrade_005.0_to_005.1.sh.in61
-rw-r--r--src/share/database/scripts/mysql/upgrade_005.1_to_005.2.sh.in47
-rw-r--r--src/share/database/scripts/mysql/upgrade_005.2_to_006.0.sh.in216
-rw-r--r--src/share/database/scripts/mysql/upgrade_006.0_to_007.0.sh.in664
-rw-r--r--src/share/database/scripts/mysql/upgrade_007.0_to_008.0.sh.in996
-rw-r--r--src/share/database/scripts/mysql/upgrade_008.0_to_008.1.sh.in123
-rw-r--r--src/share/database/scripts/mysql/upgrade_008.1_to_008.2.sh.in500
-rw-r--r--src/share/database/scripts/mysql/upgrade_008.2_to_009.0.sh.in61
-rw-r--r--src/share/database/scripts/mysql/upgrade_009.0_to_009.1.sh.in77
-rw-r--r--src/share/database/scripts/mysql/upgrade_009.1_to_009.2.sh.in81
-rw-r--r--src/share/database/scripts/mysql/upgrade_009.2_to_009.3.sh.in140
-rw-r--r--src/share/database/scripts/mysql/upgrade_009.3_to_009.4.sh.in84
-rw-r--r--src/share/database/scripts/mysql/upgrade_009.4_to_009.5.sh.in170
-rw-r--r--src/share/database/scripts/mysql/upgrade_009.5_to_009.6.sh.in70
-rw-r--r--src/share/database/scripts/mysql/upgrade_009.6_to_010.0.sh.in1029
-rw-r--r--src/share/database/scripts/mysql/upgrade_010_to_011.sh.in85
-rw-r--r--src/share/database/scripts/mysql/upgrade_011_to_012.sh.in87
-rw-r--r--src/share/database/scripts/mysql/upgrade_012_to_013.sh.in265
-rw-r--r--src/share/database/scripts/mysql/upgrade_013_to_014.sh.in826
-rw-r--r--src/share/database/scripts/mysql/wipe_data.sh.in118
-rw-r--r--src/share/database/scripts/pgsql/Makefile.am34
-rw-r--r--src/share/database/scripts/pgsql/Makefile.in880
-rw-r--r--src/share/database/scripts/pgsql/dhcpdb_create.pgsql5658
-rw-r--r--src/share/database/scripts/pgsql/dhcpdb_drop.pgsql228
-rw-r--r--src/share/database/scripts/pgsql/upgrade_001.0_to_002.0.sh.in173
-rw-r--r--src/share/database/scripts/pgsql/upgrade_002.0_to_003.0.sh.in293
-rw-r--r--src/share/database/scripts/pgsql/upgrade_003.0_to_003.1.sh.in65
-rw-r--r--src/share/database/scripts/pgsql/upgrade_003.1_to_003.2.sh.in78
-rw-r--r--src/share/database/scripts/pgsql/upgrade_003.2_to_003.3.sh.in56
-rw-r--r--src/share/database/scripts/pgsql/upgrade_003.3_to_004.0.sh.in261
-rw-r--r--src/share/database/scripts/pgsql/upgrade_004.0_to_005.0.sh.in172
-rw-r--r--src/share/database/scripts/pgsql/upgrade_005.0_to_005.1.sh.in52
-rw-r--r--src/share/database/scripts/pgsql/upgrade_005.1_to_006.0.sh.in71
-rw-r--r--src/share/database/scripts/pgsql/upgrade_006.0_to_006.1.sh.in139
-rw-r--r--src/share/database/scripts/pgsql/upgrade_006.1_to_006.2.sh.in71
-rw-r--r--src/share/database/scripts/pgsql/upgrade_006.2_to_007.0.sh.in2807
-rw-r--r--src/share/database/scripts/pgsql/upgrade_007_to_008.sh.in514
-rw-r--r--src/share/database/scripts/pgsql/upgrade_008_to_009.sh.in292
-rw-r--r--src/share/database/scripts/pgsql/upgrade_009_to_010.sh.in335
-rw-r--r--src/share/database/scripts/pgsql/upgrade_010_to_011.sh.in152
-rw-r--r--src/share/database/scripts/pgsql/upgrade_011_to_012.sh.in106
-rw-r--r--src/share/database/scripts/pgsql/upgrade_012_to_013.sh.in681
-rw-r--r--src/share/database/scripts/pgsql/wipe_data.sh.in117
-rw-r--r--src/share/yang/Makefile.am1
-rw-r--r--src/share/yang/Makefile.in704
-rw-r--r--src/share/yang/modules/Makefile.am26
-rw-r--r--src/share/yang/modules/Makefile.in774
-rw-r--r--src/share/yang/modules/hashes/ietf-dhcpv6-client@2018-09-04.hash1
-rw-r--r--src/share/yang/modules/hashes/ietf-dhcpv6-options@2018-09-04.hash1
-rw-r--r--src/share/yang/modules/hashes/ietf-dhcpv6-relay@2018-09-04.hash1
-rw-r--r--src/share/yang/modules/hashes/ietf-dhcpv6-server@2018-09-04.hash1
-rw-r--r--src/share/yang/modules/hashes/ietf-dhcpv6-types@2018-09-04.hash1
-rw-r--r--src/share/yang/modules/hashes/ietf-inet-types@2013-07-15.hash1
-rw-r--r--src/share/yang/modules/hashes/ietf-interfaces@2018-02-20.hash1
-rw-r--r--src/share/yang/modules/hashes/ietf-yang-types@2013-07-15.hash1
-rw-r--r--src/share/yang/modules/hashes/kea-ctrl-agent@2019-08-12.hash1
-rw-r--r--src/share/yang/modules/hashes/kea-dhcp-ddns@2022-07-27.hash1
-rw-r--r--src/share/yang/modules/hashes/kea-dhcp-types@2022-07-27.hash1
-rw-r--r--src/share/yang/modules/hashes/kea-dhcp4-server@2022-07-27.hash1
-rw-r--r--src/share/yang/modules/hashes/kea-dhcp6-server@2022-07-27.hash1
-rw-r--r--src/share/yang/modules/hashes/kea-types@2019-08-12.hash1
-rw-r--r--src/share/yang/modules/hashes/keatest-module@2018-11-20.hash1
-rw-r--r--src/share/yang/modules/ietf-dhcpv6-client@2018-09-04.yang346
-rw-r--r--src/share/yang/modules/ietf-dhcpv6-options@2018-09-04.yang1391
-rw-r--r--src/share/yang/modules/ietf-dhcpv6-relay@2018-09-04.yang476
-rw-r--r--src/share/yang/modules/ietf-dhcpv6-server@2018-09-04.yang967
-rw-r--r--src/share/yang/modules/ietf-dhcpv6-types@2018-09-04.yang211
-rw-r--r--src/share/yang/modules/ietf-inet-types@2013-07-15.yang457
-rw-r--r--src/share/yang/modules/ietf-interfaces@2018-02-20.yang1123
-rw-r--r--src/share/yang/modules/ietf-yang-types@2013-07-15.yang474
-rw-r--r--src/share/yang/modules/kea-ctrl-agent@2019-08-12.yang78
-rw-r--r--src/share/yang/modules/kea-dhcp-ddns@2022-07-27.yang184
-rw-r--r--src/share/yang/modules/kea-dhcp-types@2022-07-27.yang1122
-rw-r--r--src/share/yang/modules/kea-dhcp4-server@2022-07-27.yang609
-rw-r--r--src/share/yang/modules/kea-dhcp6-server@2022-07-27.yang715
-rw-r--r--src/share/yang/modules/kea-types@2019-08-12.yang159
-rw-r--r--src/share/yang/modules/keatest-module@2018-11-20.yang228
-rw-r--r--src/share/yang/modules/utils/Makefile.am27
-rw-r--r--src/share/yang/modules/utils/Makefile.in906
-rwxr-xr-xsrc/share/yang/modules/utils/check-hashes.sh54
-rwxr-xr-xsrc/share/yang/modules/utils/check-revisions.sh38
-rw-r--r--src/share/yang/modules/utils/reinstall.sh.in169
294 files changed, 47492 insertions, 0 deletions
diff --git a/src/share/Makefile.am b/src/share/Makefile.am
new file mode 100644
index 0000000..84f407e
--- /dev/null
+++ b/src/share/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = api database yang
diff --git a/src/share/Makefile.in b/src/share/Makefile.in
new file mode 100644
index 0000000..7ab92fd
--- /dev/null
+++ b/src/share/Makefile.in
@@ -0,0 +1,704 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/share
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = api database yang
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/share/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/share/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/share/api/Makefile.am b/src/share/api/Makefile.am
new file mode 100644
index 0000000..e84b630
--- /dev/null
+++ b/src/share/api/Makefile.am
@@ -0,0 +1,17 @@
+# list of api files
+api_files =
+include $(srcdir)/api_files.mk
+
+EXTRA_DIST = api_files.mk
+EXTRA_DIST += README
+EXTRA_DIST += _template.json
+EXTRA_DIST += generate-templates
+EXTRA_DIST += generate-api-files
+EXTRA_DIST += $(api_files)
+
+install-data-local:
+ mkdir -p $(DESTDIR)$(datarootdir)/${PACKAGE_NAME}/api
+ cp $(api_files) $(DESTDIR)$(datarootdir)/${PACKAGE_NAME}/api
+
+uninstall-local:
+ rm -rf $(DESTDIR)$(datarootdir)/${PACKAGE_NAME}/api
diff --git a/src/share/api/Makefile.in b/src/share/api/Makefile.in
new file mode 100644
index 0000000..a216285
--- /dev/null
+++ b/src/share/api/Makefile.in
@@ -0,0 +1,726 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/share/api
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/api_files.mk README
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# list of api files
+api_files = $(top_srcdir)/src/share/api/build-report.json \
+ $(top_srcdir)/src/share/api/cache-clear.json \
+ $(top_srcdir)/src/share/api/cache-flush.json \
+ $(top_srcdir)/src/share/api/cache-get-by-id.json \
+ $(top_srcdir)/src/share/api/cache-get.json \
+ $(top_srcdir)/src/share/api/cache-insert.json \
+ $(top_srcdir)/src/share/api/cache-load.json \
+ $(top_srcdir)/src/share/api/cache-remove.json \
+ $(top_srcdir)/src/share/api/cache-size.json \
+ $(top_srcdir)/src/share/api/cache-write.json \
+ $(top_srcdir)/src/share/api/class-add.json \
+ $(top_srcdir)/src/share/api/class-del.json \
+ $(top_srcdir)/src/share/api/class-get.json \
+ $(top_srcdir)/src/share/api/class-list.json \
+ $(top_srcdir)/src/share/api/class-update.json \
+ $(top_srcdir)/src/share/api/config-backend-pull.json \
+ $(top_srcdir)/src/share/api/config-get.json \
+ $(top_srcdir)/src/share/api/config-reload.json \
+ $(top_srcdir)/src/share/api/config-set.json \
+ $(top_srcdir)/src/share/api/config-test.json \
+ $(top_srcdir)/src/share/api/config-write.json \
+ $(top_srcdir)/src/share/api/dhcp-disable.json \
+ $(top_srcdir)/src/share/api/dhcp-enable.json \
+ $(top_srcdir)/src/share/api/gss-tsig-get-all.json \
+ $(top_srcdir)/src/share/api/gss-tsig-get.json \
+ $(top_srcdir)/src/share/api/gss-tsig-key-del.json \
+ $(top_srcdir)/src/share/api/gss-tsig-key-expire.json \
+ $(top_srcdir)/src/share/api/gss-tsig-key-get.json \
+ $(top_srcdir)/src/share/api/gss-tsig-list.json \
+ $(top_srcdir)/src/share/api/gss-tsig-purge-all.json \
+ $(top_srcdir)/src/share/api/gss-tsig-purge.json \
+ $(top_srcdir)/src/share/api/gss-tsig-rekey-all.json \
+ $(top_srcdir)/src/share/api/gss-tsig-rekey.json \
+ $(top_srcdir)/src/share/api/ha-continue.json \
+ $(top_srcdir)/src/share/api/ha-heartbeat.json \
+ $(top_srcdir)/src/share/api/ha-maintenance-cancel.json \
+ $(top_srcdir)/src/share/api/ha-maintenance-notify.json \
+ $(top_srcdir)/src/share/api/ha-maintenance-start.json \
+ $(top_srcdir)/src/share/api/ha-reset.json \
+ $(top_srcdir)/src/share/api/ha-scopes.json \
+ $(top_srcdir)/src/share/api/ha-sync.json \
+ $(top_srcdir)/src/share/api/ha-sync-complete-notify.json \
+ $(top_srcdir)/src/share/api/lease4-add.json \
+ $(top_srcdir)/src/share/api/lease4-del.json \
+ $(top_srcdir)/src/share/api/lease4-get-all.json \
+ $(top_srcdir)/src/share/api/lease4-get-by-client-id.json \
+ $(top_srcdir)/src/share/api/lease4-get-by-hostname.json \
+ $(top_srcdir)/src/share/api/lease4-get-by-hw-address.json \
+ $(top_srcdir)/src/share/api/lease4-get-page.json \
+ $(top_srcdir)/src/share/api/lease4-get.json \
+ $(top_srcdir)/src/share/api/lease4-resend-ddns.json \
+ $(top_srcdir)/src/share/api/lease4-update.json \
+ $(top_srcdir)/src/share/api/lease4-wipe.json \
+ $(top_srcdir)/src/share/api/lease6-add.json \
+ $(top_srcdir)/src/share/api/lease6-bulk-apply.json \
+ $(top_srcdir)/src/share/api/lease6-del.json \
+ $(top_srcdir)/src/share/api/lease6-get-all.json \
+ $(top_srcdir)/src/share/api/lease6-get-by-duid.json \
+ $(top_srcdir)/src/share/api/lease6-get-by-hostname.json \
+ $(top_srcdir)/src/share/api/lease6-get-page.json \
+ $(top_srcdir)/src/share/api/lease6-get.json \
+ $(top_srcdir)/src/share/api/lease6-resend-ddns.json \
+ $(top_srcdir)/src/share/api/lease6-update.json \
+ $(top_srcdir)/src/share/api/lease6-wipe.json \
+ $(top_srcdir)/src/share/api/leases-reclaim.json \
+ $(top_srcdir)/src/share/api/libreload.json \
+ $(top_srcdir)/src/share/api/list-commands.json \
+ $(top_srcdir)/src/share/api/network4-add.json \
+ $(top_srcdir)/src/share/api/network4-del.json \
+ $(top_srcdir)/src/share/api/network4-get.json \
+ $(top_srcdir)/src/share/api/network4-list.json \
+ $(top_srcdir)/src/share/api/network4-subnet-add.json \
+ $(top_srcdir)/src/share/api/network4-subnet-del.json \
+ $(top_srcdir)/src/share/api/network6-add.json \
+ $(top_srcdir)/src/share/api/network6-del.json \
+ $(top_srcdir)/src/share/api/network6-get.json \
+ $(top_srcdir)/src/share/api/network6-list.json \
+ $(top_srcdir)/src/share/api/network6-subnet-add.json \
+ $(top_srcdir)/src/share/api/network6-subnet-del.json \
+ $(top_srcdir)/src/share/api/remote-class4-del.json \
+ $(top_srcdir)/src/share/api/remote-class4-get-all.json \
+ $(top_srcdir)/src/share/api/remote-class4-get.json \
+ $(top_srcdir)/src/share/api/remote-class4-set.json \
+ $(top_srcdir)/src/share/api/remote-class6-del.json \
+ $(top_srcdir)/src/share/api/remote-class6-get-all.json \
+ $(top_srcdir)/src/share/api/remote-class6-get.json \
+ $(top_srcdir)/src/share/api/remote-class6-set.json \
+ $(top_srcdir)/src/share/api/remote-global-parameter4-del.json \
+ $(top_srcdir)/src/share/api/remote-global-parameter4-get-all.json \
+ $(top_srcdir)/src/share/api/remote-global-parameter4-get.json \
+ $(top_srcdir)/src/share/api/remote-global-parameter4-set.json \
+ $(top_srcdir)/src/share/api/remote-global-parameter6-del.json \
+ $(top_srcdir)/src/share/api/remote-global-parameter6-get-all.json \
+ $(top_srcdir)/src/share/api/remote-global-parameter6-get.json \
+ $(top_srcdir)/src/share/api/remote-global-parameter6-set.json \
+ $(top_srcdir)/src/share/api/remote-network4-del.json \
+ $(top_srcdir)/src/share/api/remote-network4-get.json \
+ $(top_srcdir)/src/share/api/remote-network4-list.json \
+ $(top_srcdir)/src/share/api/remote-network4-set.json \
+ $(top_srcdir)/src/share/api/remote-network6-del.json \
+ $(top_srcdir)/src/share/api/remote-network6-get.json \
+ $(top_srcdir)/src/share/api/remote-network6-list.json \
+ $(top_srcdir)/src/share/api/remote-network6-set.json \
+ $(top_srcdir)/src/share/api/remote-option-def4-del.json \
+ $(top_srcdir)/src/share/api/remote-option-def4-get-all.json \
+ $(top_srcdir)/src/share/api/remote-option-def4-get.json \
+ $(top_srcdir)/src/share/api/remote-option-def4-set.json \
+ $(top_srcdir)/src/share/api/remote-option-def6-del.json \
+ $(top_srcdir)/src/share/api/remote-option-def6-get-all.json \
+ $(top_srcdir)/src/share/api/remote-option-def6-get.json \
+ $(top_srcdir)/src/share/api/remote-option-def6-set.json \
+ $(top_srcdir)/src/share/api/remote-option4-global-del.json \
+ $(top_srcdir)/src/share/api/remote-option4-global-get-all.json \
+ $(top_srcdir)/src/share/api/remote-option4-global-get.json \
+ $(top_srcdir)/src/share/api/remote-option4-global-set.json \
+ $(top_srcdir)/src/share/api/remote-option4-network-del.json \
+ $(top_srcdir)/src/share/api/remote-option4-network-set.json \
+ $(top_srcdir)/src/share/api/remote-option4-pool-del.json \
+ $(top_srcdir)/src/share/api/remote-option4-pool-set.json \
+ $(top_srcdir)/src/share/api/remote-option4-subnet-del.json \
+ $(top_srcdir)/src/share/api/remote-option4-subnet-set.json \
+ $(top_srcdir)/src/share/api/remote-option6-global-del.json \
+ $(top_srcdir)/src/share/api/remote-option6-global-get-all.json \
+ $(top_srcdir)/src/share/api/remote-option6-global-get.json \
+ $(top_srcdir)/src/share/api/remote-option6-global-set.json \
+ $(top_srcdir)/src/share/api/remote-option6-network-del.json \
+ $(top_srcdir)/src/share/api/remote-option6-network-set.json \
+ $(top_srcdir)/src/share/api/remote-option6-pd-pool-del.json \
+ $(top_srcdir)/src/share/api/remote-option6-pd-pool-set.json \
+ $(top_srcdir)/src/share/api/remote-option6-pool-del.json \
+ $(top_srcdir)/src/share/api/remote-option6-pool-set.json \
+ $(top_srcdir)/src/share/api/remote-option6-subnet-del.json \
+ $(top_srcdir)/src/share/api/remote-option6-subnet-set.json \
+ $(top_srcdir)/src/share/api/remote-server4-del.json \
+ $(top_srcdir)/src/share/api/remote-server4-get-all.json \
+ $(top_srcdir)/src/share/api/remote-server4-get.json \
+ $(top_srcdir)/src/share/api/remote-server4-set.json \
+ $(top_srcdir)/src/share/api/remote-server6-del.json \
+ $(top_srcdir)/src/share/api/remote-server6-get-all.json \
+ $(top_srcdir)/src/share/api/remote-server6-get.json \
+ $(top_srcdir)/src/share/api/remote-server6-set.json \
+ $(top_srcdir)/src/share/api/remote-subnet4-del-by-id.json \
+ $(top_srcdir)/src/share/api/remote-subnet4-del-by-prefix.json \
+ $(top_srcdir)/src/share/api/remote-subnet4-get-by-id.json \
+ $(top_srcdir)/src/share/api/remote-subnet4-get-by-prefix.json \
+ $(top_srcdir)/src/share/api/remote-subnet4-list.json \
+ $(top_srcdir)/src/share/api/remote-subnet4-set.json \
+ $(top_srcdir)/src/share/api/remote-subnet6-del-by-id.json \
+ $(top_srcdir)/src/share/api/remote-subnet6-del-by-prefix.json \
+ $(top_srcdir)/src/share/api/remote-subnet6-get-by-id.json \
+ $(top_srcdir)/src/share/api/remote-subnet6-get-by-prefix.json \
+ $(top_srcdir)/src/share/api/remote-subnet6-list.json \
+ $(top_srcdir)/src/share/api/remote-subnet6-set.json \
+ $(top_srcdir)/src/share/api/reservation-add.json \
+ $(top_srcdir)/src/share/api/reservation-del.json \
+ $(top_srcdir)/src/share/api/reservation-get-all.json \
+ $(top_srcdir)/src/share/api/reservation-get-by-hostname.json \
+ $(top_srcdir)/src/share/api/reservation-get-by-id.json \
+ $(top_srcdir)/src/share/api/reservation-get-page.json \
+ $(top_srcdir)/src/share/api/reservation-get.json \
+ $(top_srcdir)/src/share/api/server-tag-get.json \
+ $(top_srcdir)/src/share/api/shutdown.json \
+ $(top_srcdir)/src/share/api/stat-lease4-get.json \
+ $(top_srcdir)/src/share/api/stat-lease6-get.json \
+ $(top_srcdir)/src/share/api/statistic-get-all.json \
+ $(top_srcdir)/src/share/api/statistic-get.json \
+ $(top_srcdir)/src/share/api/statistic-remove-all.json \
+ $(top_srcdir)/src/share/api/statistic-remove.json \
+ $(top_srcdir)/src/share/api/statistic-reset-all.json \
+ $(top_srcdir)/src/share/api/statistic-reset.json \
+ $(top_srcdir)/src/share/api/statistic-sample-age-set-all.json \
+ $(top_srcdir)/src/share/api/statistic-sample-age-set.json \
+ $(top_srcdir)/src/share/api/statistic-sample-count-set-all.json \
+ $(top_srcdir)/src/share/api/statistic-sample-count-set.json \
+ $(top_srcdir)/src/share/api/status-get.json \
+ $(top_srcdir)/src/share/api/subnet4-add.json \
+ $(top_srcdir)/src/share/api/subnet4-del.json \
+ $(top_srcdir)/src/share/api/subnet4-delta-add.json \
+ $(top_srcdir)/src/share/api/subnet4-delta-del.json \
+ $(top_srcdir)/src/share/api/subnet4-get.json \
+ $(top_srcdir)/src/share/api/subnet4-list.json \
+ $(top_srcdir)/src/share/api/subnet4-update.json \
+ $(top_srcdir)/src/share/api/subnet6-add.json \
+ $(top_srcdir)/src/share/api/subnet6-del.json \
+ $(top_srcdir)/src/share/api/subnet6-delta-add.json \
+ $(top_srcdir)/src/share/api/subnet6-delta-del.json \
+ $(top_srcdir)/src/share/api/subnet6-get.json \
+ $(top_srcdir)/src/share/api/subnet6-list.json \
+ $(top_srcdir)/src/share/api/subnet6-update.json \
+ $(top_srcdir)/src/share/api/version-get.json
+EXTRA_DIST = api_files.mk README _template.json generate-templates \
+ generate-api-files $(api_files)
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(srcdir)/api_files.mk $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/share/api/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/share/api/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(srcdir)/api_files.mk $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-data-local
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-local
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am \
+ install-data-local install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags-am uninstall \
+ uninstall-am uninstall-local
+
+.PRECIOUS: Makefile
+
+
+install-data-local:
+ mkdir -p $(DESTDIR)$(datarootdir)/${PACKAGE_NAME}/api
+ cp $(api_files) $(DESTDIR)$(datarootdir)/${PACKAGE_NAME}/api
+
+uninstall-local:
+ rm -rf $(DESTDIR)$(datarootdir)/${PACKAGE_NAME}/api
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/share/api/README b/src/share/api/README
new file mode 100644
index 0000000..fa659f6
--- /dev/null
+++ b/src/share/api/README
@@ -0,0 +1,27 @@
+There are several steps needed to document new API command:
+
+1. (optional) run: ./generate-templates cmd1 cmd2...
+ This will go through the list of commands given in arguments
+ and will check if there are corresponding JSON files in name.json
+ If the file is missing, a new JSON will be created using template.
+ If you dislike this generator, you can always use _template.json
+ and copy it over under the name of a new command.
+2. Edit command-name.json. If the command is provided by the daemon
+ out of its own (and not via hook), simply delete the hook entry.
+ If you don't want to provide command syntax (cmd-syntax key),
+ any comments about the syntax (cmd-comment key) or response syntax
+ (resp-syntax) or any comment about response (resp-comment), simply
+ remove those unused keys. The generator will attempt to generate
+ boilerplates for it.
+3. Update api_files.mk. You can also run: ./generate-api-files > api_files.mk
+ or check the update by: ./generate-api-files | diff - api_files.mk
+4. Rebuild User's Guide as usual, run in doc/sphinx folder: make
+
+Files in this directory:
+ - README: this file
+ - _template.json: template used by generate-templates
+ - api-files.mk: list of command files for inclusion in Makefiles
+ (can be build by ./generate-api-files)
+ - generate-templates: script generating new command files from the
+ the template (_template.json)
+ - generate-api-files: script generating api-files.mk
diff --git a/src/share/api/_template.json b/src/share/api/_template.json
new file mode 100644
index 0000000..4ccbb9f
--- /dev/null
+++ b/src/share/api/_template.json
@@ -0,0 +1,48 @@
+// Note the *.json files are to be processed by api2doc.py and possibly other
+// JSON tools that do not handle comments. If using this template, make sure you
+// have all the comments removed.
+{
+ // This specifies the name of the command. Must be the same as the filename.
+ "name": "template-command",
+
+ // This provides a short description. Since JSON does not support multi-line
+ // strings, each line is a separate string. Feel free to have one long string,
+ // or several lines of nicely wrapped text.
+ "brief": [ "a sentence or two explaining what it is" ],
+
+ // This provides a full description. Since we have most (all?) commands
+ // already documented, we don't want to copy over the text. Instead,
+ // we provide links to specific section.
+ "description": "See :ref:`anchor-in-the-docs`/>",
+
+ // This enumerates the daemons that are able to handle specific commands.
+ // Usually this will be a subset of those specified below as there are very
+ // few commands that are supported by every daemon.
+ "support": [ "kea-dhcp4", "kea-dhcp6", "dhcp-ddns", "ca", "netconf" ],
+
+ // Historical information. May need a bit of digging, but this info can
+ // be extracted by looking at older Kea user guides. We have them published
+ // on FTP.
+ "avail": "first version the command appeared in",
+
+ // Many commands are provided by a hook. Specify the name of the hook here.
+ // If this hook is provided by the daemons natively (without needing to load
+ // a hook), remove the whole "hook" entry.
+ "hook": "name of the hook that provides this command. remove if provided by the core code",
+
+ // This defines the read or write access. Default is write access
+ // to not leave unrestricted access to a dangerous command by accident.
+ "access": "write",
+
+ // This defines the syntax of the command.
+ "cmd-syntax": [ "Syntax of the command" ],
+
+ // This defines a description printed immediately below the command syntax.
+ "cmd-comment": [ "Possibly some extra comments after the syntax." ],
+
+ // This defines the syntax of the response.
+ "resp-syntax": [ "Syntax of the response" ],
+
+ // This defines a description printed immediately below the response syntax.
+ "resp-comment": [ "Optional extra comments after the response syntax." ]
+}
diff --git a/src/share/api/api_files.mk b/src/share/api/api_files.mk
new file mode 100644
index 0000000..cc05aba
--- /dev/null
+++ b/src/share/api/api_files.mk
@@ -0,0 +1,190 @@
+api_files += $(top_srcdir)/src/share/api/build-report.json
+api_files += $(top_srcdir)/src/share/api/cache-clear.json
+api_files += $(top_srcdir)/src/share/api/cache-flush.json
+api_files += $(top_srcdir)/src/share/api/cache-get-by-id.json
+api_files += $(top_srcdir)/src/share/api/cache-get.json
+api_files += $(top_srcdir)/src/share/api/cache-insert.json
+api_files += $(top_srcdir)/src/share/api/cache-load.json
+api_files += $(top_srcdir)/src/share/api/cache-remove.json
+api_files += $(top_srcdir)/src/share/api/cache-size.json
+api_files += $(top_srcdir)/src/share/api/cache-write.json
+api_files += $(top_srcdir)/src/share/api/class-add.json
+api_files += $(top_srcdir)/src/share/api/class-del.json
+api_files += $(top_srcdir)/src/share/api/class-get.json
+api_files += $(top_srcdir)/src/share/api/class-list.json
+api_files += $(top_srcdir)/src/share/api/class-update.json
+api_files += $(top_srcdir)/src/share/api/config-backend-pull.json
+api_files += $(top_srcdir)/src/share/api/config-get.json
+api_files += $(top_srcdir)/src/share/api/config-reload.json
+api_files += $(top_srcdir)/src/share/api/config-set.json
+api_files += $(top_srcdir)/src/share/api/config-test.json
+api_files += $(top_srcdir)/src/share/api/config-write.json
+api_files += $(top_srcdir)/src/share/api/dhcp-disable.json
+api_files += $(top_srcdir)/src/share/api/dhcp-enable.json
+api_files += $(top_srcdir)/src/share/api/gss-tsig-get-all.json
+api_files += $(top_srcdir)/src/share/api/gss-tsig-get.json
+api_files += $(top_srcdir)/src/share/api/gss-tsig-key-del.json
+api_files += $(top_srcdir)/src/share/api/gss-tsig-key-expire.json
+api_files += $(top_srcdir)/src/share/api/gss-tsig-key-get.json
+api_files += $(top_srcdir)/src/share/api/gss-tsig-list.json
+api_files += $(top_srcdir)/src/share/api/gss-tsig-purge-all.json
+api_files += $(top_srcdir)/src/share/api/gss-tsig-purge.json
+api_files += $(top_srcdir)/src/share/api/gss-tsig-rekey-all.json
+api_files += $(top_srcdir)/src/share/api/gss-tsig-rekey.json
+api_files += $(top_srcdir)/src/share/api/ha-continue.json
+api_files += $(top_srcdir)/src/share/api/ha-heartbeat.json
+api_files += $(top_srcdir)/src/share/api/ha-maintenance-cancel.json
+api_files += $(top_srcdir)/src/share/api/ha-maintenance-notify.json
+api_files += $(top_srcdir)/src/share/api/ha-maintenance-start.json
+api_files += $(top_srcdir)/src/share/api/ha-reset.json
+api_files += $(top_srcdir)/src/share/api/ha-scopes.json
+api_files += $(top_srcdir)/src/share/api/ha-sync.json
+api_files += $(top_srcdir)/src/share/api/ha-sync-complete-notify.json
+api_files += $(top_srcdir)/src/share/api/lease4-add.json
+api_files += $(top_srcdir)/src/share/api/lease4-del.json
+api_files += $(top_srcdir)/src/share/api/lease4-get-all.json
+api_files += $(top_srcdir)/src/share/api/lease4-get-by-client-id.json
+api_files += $(top_srcdir)/src/share/api/lease4-get-by-hostname.json
+api_files += $(top_srcdir)/src/share/api/lease4-get-by-hw-address.json
+api_files += $(top_srcdir)/src/share/api/lease4-get-page.json
+api_files += $(top_srcdir)/src/share/api/lease4-get.json
+api_files += $(top_srcdir)/src/share/api/lease4-resend-ddns.json
+api_files += $(top_srcdir)/src/share/api/lease4-update.json
+api_files += $(top_srcdir)/src/share/api/lease4-wipe.json
+api_files += $(top_srcdir)/src/share/api/lease6-add.json
+api_files += $(top_srcdir)/src/share/api/lease6-bulk-apply.json
+api_files += $(top_srcdir)/src/share/api/lease6-del.json
+api_files += $(top_srcdir)/src/share/api/lease6-get-all.json
+api_files += $(top_srcdir)/src/share/api/lease6-get-by-duid.json
+api_files += $(top_srcdir)/src/share/api/lease6-get-by-hostname.json
+api_files += $(top_srcdir)/src/share/api/lease6-get-page.json
+api_files += $(top_srcdir)/src/share/api/lease6-get.json
+api_files += $(top_srcdir)/src/share/api/lease6-resend-ddns.json
+api_files += $(top_srcdir)/src/share/api/lease6-update.json
+api_files += $(top_srcdir)/src/share/api/lease6-wipe.json
+api_files += $(top_srcdir)/src/share/api/leases-reclaim.json
+api_files += $(top_srcdir)/src/share/api/libreload.json
+api_files += $(top_srcdir)/src/share/api/list-commands.json
+api_files += $(top_srcdir)/src/share/api/network4-add.json
+api_files += $(top_srcdir)/src/share/api/network4-del.json
+api_files += $(top_srcdir)/src/share/api/network4-get.json
+api_files += $(top_srcdir)/src/share/api/network4-list.json
+api_files += $(top_srcdir)/src/share/api/network4-subnet-add.json
+api_files += $(top_srcdir)/src/share/api/network4-subnet-del.json
+api_files += $(top_srcdir)/src/share/api/network6-add.json
+api_files += $(top_srcdir)/src/share/api/network6-del.json
+api_files += $(top_srcdir)/src/share/api/network6-get.json
+api_files += $(top_srcdir)/src/share/api/network6-list.json
+api_files += $(top_srcdir)/src/share/api/network6-subnet-add.json
+api_files += $(top_srcdir)/src/share/api/network6-subnet-del.json
+api_files += $(top_srcdir)/src/share/api/remote-class4-del.json
+api_files += $(top_srcdir)/src/share/api/remote-class4-get-all.json
+api_files += $(top_srcdir)/src/share/api/remote-class4-get.json
+api_files += $(top_srcdir)/src/share/api/remote-class4-set.json
+api_files += $(top_srcdir)/src/share/api/remote-class6-del.json
+api_files += $(top_srcdir)/src/share/api/remote-class6-get-all.json
+api_files += $(top_srcdir)/src/share/api/remote-class6-get.json
+api_files += $(top_srcdir)/src/share/api/remote-class6-set.json
+api_files += $(top_srcdir)/src/share/api/remote-global-parameter4-del.json
+api_files += $(top_srcdir)/src/share/api/remote-global-parameter4-get-all.json
+api_files += $(top_srcdir)/src/share/api/remote-global-parameter4-get.json
+api_files += $(top_srcdir)/src/share/api/remote-global-parameter4-set.json
+api_files += $(top_srcdir)/src/share/api/remote-global-parameter6-del.json
+api_files += $(top_srcdir)/src/share/api/remote-global-parameter6-get-all.json
+api_files += $(top_srcdir)/src/share/api/remote-global-parameter6-get.json
+api_files += $(top_srcdir)/src/share/api/remote-global-parameter6-set.json
+api_files += $(top_srcdir)/src/share/api/remote-network4-del.json
+api_files += $(top_srcdir)/src/share/api/remote-network4-get.json
+api_files += $(top_srcdir)/src/share/api/remote-network4-list.json
+api_files += $(top_srcdir)/src/share/api/remote-network4-set.json
+api_files += $(top_srcdir)/src/share/api/remote-network6-del.json
+api_files += $(top_srcdir)/src/share/api/remote-network6-get.json
+api_files += $(top_srcdir)/src/share/api/remote-network6-list.json
+api_files += $(top_srcdir)/src/share/api/remote-network6-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option-def4-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option-def4-get-all.json
+api_files += $(top_srcdir)/src/share/api/remote-option-def4-get.json
+api_files += $(top_srcdir)/src/share/api/remote-option-def4-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option-def6-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option-def6-get-all.json
+api_files += $(top_srcdir)/src/share/api/remote-option-def6-get.json
+api_files += $(top_srcdir)/src/share/api/remote-option-def6-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option4-global-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option4-global-get-all.json
+api_files += $(top_srcdir)/src/share/api/remote-option4-global-get.json
+api_files += $(top_srcdir)/src/share/api/remote-option4-global-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option4-network-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option4-network-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option4-pool-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option4-pool-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option4-subnet-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option4-subnet-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-global-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-global-get-all.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-global-get.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-global-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-network-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-network-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-pd-pool-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-pd-pool-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-pool-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-pool-set.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-subnet-del.json
+api_files += $(top_srcdir)/src/share/api/remote-option6-subnet-set.json
+api_files += $(top_srcdir)/src/share/api/remote-server4-del.json
+api_files += $(top_srcdir)/src/share/api/remote-server4-get-all.json
+api_files += $(top_srcdir)/src/share/api/remote-server4-get.json
+api_files += $(top_srcdir)/src/share/api/remote-server4-set.json
+api_files += $(top_srcdir)/src/share/api/remote-server6-del.json
+api_files += $(top_srcdir)/src/share/api/remote-server6-get-all.json
+api_files += $(top_srcdir)/src/share/api/remote-server6-get.json
+api_files += $(top_srcdir)/src/share/api/remote-server6-set.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet4-del-by-id.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet4-del-by-prefix.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet4-get-by-id.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet4-get-by-prefix.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet4-list.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet4-set.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet6-del-by-id.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet6-del-by-prefix.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet6-get-by-id.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet6-get-by-prefix.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet6-list.json
+api_files += $(top_srcdir)/src/share/api/remote-subnet6-set.json
+api_files += $(top_srcdir)/src/share/api/reservation-add.json
+api_files += $(top_srcdir)/src/share/api/reservation-del.json
+api_files += $(top_srcdir)/src/share/api/reservation-get-all.json
+api_files += $(top_srcdir)/src/share/api/reservation-get-by-hostname.json
+api_files += $(top_srcdir)/src/share/api/reservation-get-by-id.json
+api_files += $(top_srcdir)/src/share/api/reservation-get-page.json
+api_files += $(top_srcdir)/src/share/api/reservation-get.json
+api_files += $(top_srcdir)/src/share/api/server-tag-get.json
+api_files += $(top_srcdir)/src/share/api/shutdown.json
+api_files += $(top_srcdir)/src/share/api/stat-lease4-get.json
+api_files += $(top_srcdir)/src/share/api/stat-lease6-get.json
+api_files += $(top_srcdir)/src/share/api/statistic-get-all.json
+api_files += $(top_srcdir)/src/share/api/statistic-get.json
+api_files += $(top_srcdir)/src/share/api/statistic-remove-all.json
+api_files += $(top_srcdir)/src/share/api/statistic-remove.json
+api_files += $(top_srcdir)/src/share/api/statistic-reset-all.json
+api_files += $(top_srcdir)/src/share/api/statistic-reset.json
+api_files += $(top_srcdir)/src/share/api/statistic-sample-age-set-all.json
+api_files += $(top_srcdir)/src/share/api/statistic-sample-age-set.json
+api_files += $(top_srcdir)/src/share/api/statistic-sample-count-set-all.json
+api_files += $(top_srcdir)/src/share/api/statistic-sample-count-set.json
+api_files += $(top_srcdir)/src/share/api/status-get.json
+api_files += $(top_srcdir)/src/share/api/subnet4-add.json
+api_files += $(top_srcdir)/src/share/api/subnet4-del.json
+api_files += $(top_srcdir)/src/share/api/subnet4-delta-add.json
+api_files += $(top_srcdir)/src/share/api/subnet4-delta-del.json
+api_files += $(top_srcdir)/src/share/api/subnet4-get.json
+api_files += $(top_srcdir)/src/share/api/subnet4-list.json
+api_files += $(top_srcdir)/src/share/api/subnet4-update.json
+api_files += $(top_srcdir)/src/share/api/subnet6-add.json
+api_files += $(top_srcdir)/src/share/api/subnet6-del.json
+api_files += $(top_srcdir)/src/share/api/subnet6-delta-add.json
+api_files += $(top_srcdir)/src/share/api/subnet6-delta-del.json
+api_files += $(top_srcdir)/src/share/api/subnet6-get.json
+api_files += $(top_srcdir)/src/share/api/subnet6-list.json
+api_files += $(top_srcdir)/src/share/api/subnet6-update.json
+api_files += $(top_srcdir)/src/share/api/version-get.json
diff --git a/src/share/api/build-report.json b/src/share/api/build-report.json
new file mode 100644
index 0000000..bd09c08
--- /dev/null
+++ b/src/share/api/build-report.json
@@ -0,0 +1,28 @@
+{
+ "access": "read",
+ "avail": "1.2.0",
+ "brief": [
+ "This command returns the list of compilation options that this particular binary was built with."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"build-report\"",
+ "}"
+ ],
+ "name": "build-report",
+ "resp-comment": [
+ ""
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": <string with build details>",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns",
+ "kea-ctrl-agent"
+ ]
+}
diff --git a/src/share/api/cache-clear.json b/src/share/api/cache-clear.json
new file mode 100644
index 0000000..47ef877
--- /dev/null
+++ b/src/share/api/cache-clear.json
@@ -0,0 +1,13 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command removes all cached host reservations."
+ ],
+ "hook": "host_cache",
+ "name": "cache-clear",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/cache-flush.json b/src/share/api/cache-flush.json
new file mode 100644
index 0000000..be7371b
--- /dev/null
+++ b/src/share/api/cache-flush.json
@@ -0,0 +1,19 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command removes up to the given number or all cached host reservations."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"cache-flush\",",
+ " \"arguments\": 5",
+ "}"
+ ],
+ "hook": "host_cache",
+ "name": "cache-flush",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/cache-get-by-id.json b/src/share/api/cache-get-by-id.json
new file mode 100644
index 0000000..64bb8fd
--- /dev/null
+++ b/src/share/api/cache-get-by-id.json
@@ -0,0 +1,28 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command returns entries matching the given identifier from the host cache."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"cache-get-by-id\",",
+ " \"arguments\": {",
+ " \"hw-address\": \"01:02:03:04:05:06\"",
+ " }",
+ "}"
+ ],
+ "hook": "host_cache",
+ "name": "cache-get-by-id",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 entries returned.\",",
+ " \"arguments\": <list of host reservations>",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/cache-get.json b/src/share/api/cache-get.json
new file mode 100644
index 0000000..0ea703e
--- /dev/null
+++ b/src/share/api/cache-get.json
@@ -0,0 +1,20 @@
+{
+ "access": "read",
+ "avail": "1.4.0",
+ "brief": [
+ "This command returns the full content of the host cache."
+ ],
+ "hook": "host_cache",
+ "name": "cache-get",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"123 entries returned.\",",
+ " \"arguments\": <list of host reservations>",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/cache-insert.json b/src/share/api/cache-insert.json
new file mode 100644
index 0000000..83d78d9
--- /dev/null
+++ b/src/share/api/cache-insert.json
@@ -0,0 +1,53 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command inserts a host into the cache."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"cache-insert\",",
+ " \"arguments\": {",
+ " \"hw-address\": \"01:02:03:04:05:06\",",
+ " \"subnet-id4\": 4,",
+ " \"subnet-id6\": 0,",
+ " \"ip-address\": \"192.0.2.100\",",
+ " \"hostname\": \"somehost.example.org\",",
+ " \"client-classes4\": [ ],",
+ " \"client-classes6\": [ ],",
+ " \"option-data4\": [ ],",
+ " \"option-data6\": [ ],",
+ " \"next-server\": \"192.0.0.2\",",
+ " \"server-hostname\": \"server-hostname.example.org\",",
+ " \"boot-file-name\": \"bootfile.efi\",",
+ " \"host-id\": 0",
+ " }",
+ "},",
+ "{",
+ " \"command\": \"cache-insert\",",
+ " \"arguments\": {",
+ " \"hw-address\": \"01:02:03:04:05:06\",",
+ " \"subnet-id4\": 0,",
+ " \"subnet-id6\": 6,",
+ " \"ip-addresses\": [ \"2001:db8::cafe:babe\" ],",
+ " \"prefixes\": [ \"2001:db8:dead:beef::/64\" ],",
+ " \"hostname\": \"\",",
+ " \"client-classes4\": [ ],",
+ " \"client-classes6\": [ ],",
+ " \"option-data4\": [ ],",
+ " \"option-data6\": [ ],",
+ " \"next-server\": \"0.0.0.0\",",
+ " \"server-hostname\": \"\",",
+ " \"boot-file-name\": \"\",",
+ " \"host-id\": 0",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"host-reservation-v4\"/>",
+ "hook": "host_cache",
+ "name": "cache-insert",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/cache-load.json b/src/share/api/cache-load.json
new file mode 100644
index 0000000..ed06c48
--- /dev/null
+++ b/src/share/api/cache-load.json
@@ -0,0 +1,20 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command allows the contents of a file on disk to be loaded into an in-memory cache."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"cache-load\",",
+ " \"arguments\": \"/tmp/kea-host-cache.json\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-cache-load\"/>",
+ "hook": "host_cache",
+ "name": "cache-load",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/cache-remove.json b/src/share/api/cache-remove.json
new file mode 100644
index 0000000..ccf6674
--- /dev/null
+++ b/src/share/api/cache-remove.json
@@ -0,0 +1,32 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command removes entries from the host cache. It takes parameters similar to the ``reservation-get`` command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"cache-remove\",",
+ " \"arguments\": {",
+ " \"ip-address\": \"192.0.2.1\",",
+ " \"subnet-id\": 123",
+ " }",
+ "}",
+ "",
+ "Another example that removes the IPv6 host identifier by DUID and specific subnet-id is:",
+ "{",
+ " \"command\": \"cache-remove\",",
+ " \"arguments\": {",
+ " \"duid\": \"00:01:ab:cd:f0:a1:c2:d3:e4\",",
+ " \"subnet-id\": 123",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-cache-remove\"/>",
+ "hook": "host_cache",
+ "name": "cache-remove",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/cache-size.json b/src/share/api/cache-size.json
new file mode 100644
index 0000000..b92b00c
--- /dev/null
+++ b/src/share/api/cache-size.json
@@ -0,0 +1,20 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command returns the number of entries in the host cache."
+ ],
+ "hook": "host_cache",
+ "name": "cache-size",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"123 entries.\",",
+ " \"arguments\": { \"size\": 123 }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/cache-write.json b/src/share/api/cache-write.json
new file mode 100644
index 0000000..3d6850a
--- /dev/null
+++ b/src/share/api/cache-write.json
@@ -0,0 +1,22 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command instructs Kea to write its host cache content to disk."
+ ],
+ "cmd-comment": [
+ "The command takes one mandatory argument that specifies the filename path of a file to be written."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"cache-write\",",
+ " \"arguments\": \"/path/to/the/file.json\"",
+ "}"
+ ],
+ "hook": "host_cache",
+ "name": "cache-write",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/class-add.json b/src/share/api/class-add.json
new file mode 100644
index 0000000..07d8762
--- /dev/null
+++ b/src/share/api/class-add.json
@@ -0,0 +1,42 @@
+{
+ "access": "write",
+ "avail": "1.5.0",
+ "brief": [
+ "This command adds a new class to the existing server configuration."
+ ],
+ "cmd-comment": [
+ "The ``next-server``, ``server-hostname``, and ``boot-file-name`` are DHCPv4-specific. Only one client class can be added with a single command. "
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"class-add\",",
+ " \"arguments\": {",
+ " \"client-classes\": [ {",
+ " \"name\": <name of the class>,",
+ " \"test\": <test expression to be evaluated on incoming packets>,",
+ " \"option-data\": [ <option values here> ],",
+ " \"option-def\": [ <option definitions here> ],",
+ " \"next-server\": <ipv4 address>,",
+ " \"server-hostname\": <string>,",
+ " \"boot-file-name\": <name of the boot file>",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-class-add\"/>",
+ "hook": "class_cmds",
+ "name": "class-add",
+ "resp-comment": [
+ "The command is successful (result 0), unless the class name is a duplicate or another error occurs (result 1)."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"Class '<class-name>' added.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/class-del.json b/src/share/api/class-del.json
new file mode 100644
index 0000000..38c4818
--- /dev/null
+++ b/src/share/api/class-del.json
@@ -0,0 +1,31 @@
+{
+ "access": "write",
+ "avail": "1.5.0",
+ "brief": [
+ "This command removes a client class from the server configuration."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"class-del\",",
+ " \"arguments\": {",
+ " \"name\": <name of the class>",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-class-del\"/>",
+ "hook": "class_cmds",
+ "name": "class-del",
+ "resp-comment": [
+ "The command returns a result of 3 (empty) if the client class does not exist. If the client class exists, the returned result is 0 if the deletion was successful; the result is 1 if the deletion is unsuccessful."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"Class '<class-name>' deleted.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/class-get.json b/src/share/api/class-get.json
new file mode 100644
index 0000000..1d7c5b2
--- /dev/null
+++ b/src/share/api/class-get.json
@@ -0,0 +1,45 @@
+{
+ "access": "read",
+ "avail": "1.5.0",
+ "brief": [
+ "This command returns detailed information about an existing client class."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"class-get\",",
+ " \"arguments\": {",
+ " \"name\": <name of the class>",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-class-get\"/>",
+ "hook": "class_cmds",
+ "name": "class-get",
+ "resp-comment": [
+ "The returned information depends on the DHCP server type, i.e. some parameters are specific to the DHCPv4 server. Also, some parameters may not be returned if they are not set for the client class. If a class with the specified name does not exist, a result of 3 (empty) is returned. If the client class is found, the result of 0 is returned. If there is an error while processing the command, the result of 1 is returned."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"Class '<class-name>' definition returned\",",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " \"name\": <name of the class>,",
+ " \"only-if-required\": <only if required boolean value>,",
+ " \"test\": <test expression to be evaluated on incoming packets>,",
+ " \"option-data\": [ <option values here> ],",
+ " \"option-def\": [ <option definitions here> ],",
+ " \"next-server\": <ipv4 address>,",
+ " \"server-hostname\": <string>,",
+ " \"boot-file-name\": <name of the boot file>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/class-list.json b/src/share/api/class-list.json
new file mode 100644
index 0000000..17544c8
--- /dev/null
+++ b/src/share/api/class-list.json
@@ -0,0 +1,48 @@
+{
+ "access": "read",
+ "avail": "1.5.0",
+ "brief": [
+ "This command retrieves a list of all client classes from the server configuration."
+ ],
+ "cmd-comment": [
+ "This command includes no arguments."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"class-list\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-class-list\"/>",
+ "hook": "class_cmds",
+ "name": "class-list",
+ "resp-comment": [
+ "The returned list of classes merely contains their names.",
+ "In order to retrieve full information about one of these",
+ "classes, use :ref:`command-class-get`.",
+ "The returned result is 3 (empty) if no classes are found.",
+ "If the command is processed successfully and the list of",
+ "client classes is not empty, the result of 0 is returned.",
+ "If there is an error while processing the command, the",
+ "result of 1 is returned."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"'<number of>' classes found\",",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " \"name\": <first class name>",
+ " },",
+ " {",
+ " \"name\": <second class name>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/class-update.json b/src/share/api/class-update.json
new file mode 100644
index 0000000..c3b8284
--- /dev/null
+++ b/src/share/api/class-update.json
@@ -0,0 +1,42 @@
+{
+ "access": "write",
+ "avail": "1.5.0",
+ "brief": [
+ "This command updates an existing client class in the server configuration."
+ ],
+ "cmd-comment": [
+ "The ``next-server``, ``server-hostname``, and ``boot-file-name`` are DHCPv4-specific. Only one client class can be updated with a single command. "
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"class-update\",",
+ " \"arguments\": {",
+ " \"client-classes\": [ {",
+ " \"name\": <name of the class>,",
+ " \"test\": <test expression to be evaluated on incoming packets>,",
+ " \"option-data\": [ <option values here> ],",
+ " \"option-def\": [ <option definitions here> ],",
+ " \"next-server\": <ipv4 address>,",
+ " \"server-hostname\": <string>,",
+ " \"boot-file-name\": <name of the boot file>",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-class-update\"/>",
+ "hook": "class_cmds",
+ "name": "class-update",
+ "resp-comment": [
+ "The command returns the result of 3 (empty) if the client class does not exist. If the client class exists, the returned result is 0 if the update was successful, or 1 if the update is unsuccessful."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"Class '<class-name>' updated.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/config-backend-pull.json b/src/share/api/config-backend-pull.json
new file mode 100644
index 0000000..db13b16
--- /dev/null
+++ b/src/share/api/config-backend-pull.json
@@ -0,0 +1,31 @@
+{
+ "access": "write",
+ "avail": "1.7.1",
+ "brief": [
+ "This command forces an immediate update of the server using Config Backends.",
+ "This command does not take any parameters."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"config-backend-pull\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-config-backend-pull\"/>",
+ "name": "config-backend-pull",
+ "resp-comment": [
+ "When no Config Backends are configured this command returns empty (3); ",
+ "If an error occurs error (1) is returned with the error details; ",
+ "otherwise success (0) is returned."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"On demand configuration update successful.\"",
+ "}"
+ ],
+
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/config-get.json b/src/share/api/config-get.json
new file mode 100644
index 0000000..3a49afb
--- /dev/null
+++ b/src/share/api/config-get.json
@@ -0,0 +1,30 @@
+{
+ "access": "read",
+ "avail": "1.2.0",
+ "brief": [
+ "This command retrieves the current configuration used by the server. The configuration is essentially the same as the contents of the configuration file, but includes additional changes made by other commands and due to parameters' inheritance."
+ ],
+ "cmd-comment": [
+ "This command takes no parameters."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"config-get\"",
+ "}"
+ ],
+ "name": "config-get",
+ "resp-syntax": [
+ "{",
+ " \"result\": <integer>,",
+ " \"arguments\": {",
+ " <Dhcp4, Dhcp6, or Control-agent object>: <JSON configuration here> ",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns",
+ "kea-ctrl-agent"
+ ]
+}
diff --git a/src/share/api/config-reload.json b/src/share/api/config-reload.json
new file mode 100644
index 0000000..0df6326
--- /dev/null
+++ b/src/share/api/config-reload.json
@@ -0,0 +1,20 @@
+{
+ "access": "write",
+ "avail": "1.2.0",
+ "brief": [
+ "This command instructs Kea to reload the configuration file that was used previously."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"config-reload\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-config-reload\"/>",
+ "name": "config-reload",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns",
+ "kea-ctrl-agent"
+ ]
+}
diff --git a/src/share/api/config-set.json b/src/share/api/config-set.json
new file mode 100644
index 0000000..d42f4c5
--- /dev/null
+++ b/src/share/api/config-set.json
@@ -0,0 +1,34 @@
+{
+ "access": "write",
+ "avail": "1.2.0",
+ "brief": [
+ "This command instructs the server to replace its current configuration with the new configuration supplied in the command's arguments."
+ ],
+ "cmd-comment": [
+ "In the example below, '<server>' is the configuration element name for a given server such as \"Dhcp4\" or \"Dhcp6\"."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"config-set\",",
+ " \"arguments\": {",
+ " \"'<server>'\": {",
+ " }",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-config-set\"/>",
+ "name": "config-set",
+ "resp-syntax": [
+ "{\"result\": 0, \"text\": \"Configuration successful.\" }",
+ "",
+ "or",
+ "",
+ "{\"result\": 1, \"text\": \"unsupported parameter: BOGUS (<string>:16:26)\" }"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns",
+ "kea-ctrl-agent"
+ ]
+}
diff --git a/src/share/api/config-test.json b/src/share/api/config-test.json
new file mode 100644
index 0000000..0e8b83a
--- /dev/null
+++ b/src/share/api/config-test.json
@@ -0,0 +1,34 @@
+{
+ "access": "write",
+ "avail": "1.2.0",
+ "brief": [
+ "This command instructs the server to check whether the new configuration supplied in the command's arguments can be loaded."
+ ],
+ "cmd-comment": [
+ "In the example below, <server> is the configuration element name for a given server such as \"Dhcp4\" or \"Dhcp6\"."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"config-test\",",
+ " \"arguments\": {",
+ " \"'<server>'\": {",
+ " }",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-config-test\"/>",
+ "name": "config-test",
+ "resp-syntax": [
+ "{ \"result\": 0, \"text\": \"Configuration seems sane...\" }",
+ "",
+ "or",
+ "",
+ "{ \"result\": 1, \"text\": \"unsupported parameter: BOGUS (<string>:16:26)\" }"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns",
+ "kea-ctrl-agent"
+ ]
+}
diff --git a/src/share/api/config-write.json b/src/share/api/config-write.json
new file mode 100644
index 0000000..8aa3da4
--- /dev/null
+++ b/src/share/api/config-write.json
@@ -0,0 +1,24 @@
+{
+ "access": "write",
+ "avail": "1.2.0",
+ "brief": [
+ "This command instructs the Kea server to write its",
+ "current configuration to a file on disk."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"config-write\",",
+ " \"arguments\": {",
+ " \"filename\": \"config-modified-2017-03-15.json\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-config-write\"/>",
+ "name": "config-write",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns",
+ "kea-ctrl-agent"
+ ]
+}
diff --git a/src/share/api/dhcp-disable.json b/src/share/api/dhcp-disable.json
new file mode 100644
index 0000000..a12764c
--- /dev/null
+++ b/src/share/api/dhcp-disable.json
@@ -0,0 +1,22 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command globally disables the DHCP service."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"dhcp-disable\",",
+ " \"arguments\": {",
+ " \"max-period\": 20,",
+ " \"origin\": \"user\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-dhcp-disable\"/>",
+ "name": "dhcp-disable",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/dhcp-enable.json b/src/share/api/dhcp-enable.json
new file mode 100644
index 0000000..d7a9959
--- /dev/null
+++ b/src/share/api/dhcp-enable.json
@@ -0,0 +1,21 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command globally enables the DHCP service."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"dhcp-enable\",",
+ " \"arguments\": {",
+ " \"origin\": \"user\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-dhcp-enable\"/>",
+ "name": "dhcp-enable",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/generate-api-files b/src/share/api/generate-api-files
new file mode 100755
index 0000000..ff223cf
--- /dev/null
+++ b/src/share/api/generate-api-files
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# This script generates api_files.mk
+# Usage:
+#
+# ./generate-api-files > api_files.mk
+
+if [ $# -ne 0 ]; then
+ echo "Usage: ./generate-api-files"
+ exit
+fi
+
+for f in [a-z]*.json ; do
+ echo "api_files += \$(top_srcdir)/src/share/api/$f"
+done
diff --git a/src/share/api/generate-templates b/src/share/api/generate-templates
new file mode 100755
index 0000000..1bfc9ec
--- /dev/null
+++ b/src/share/api/generate-templates
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+# This script generates API documentation templates.
+# Usage:
+#
+# ./generate-templates cmd...
+#
+# Arguments are a list of new commands.
+# The templates will be created in local directory.
+
+if [ $# -eq 0 ]; then
+ echo "Usage: ./generate-templates cmd..."
+ exit
+fi
+
+while [ $# -ne 0 ]; do
+ CMD="$1"
+ shift
+ F=$CMD.json
+
+ if [ -e "$F" ]; then
+ echo "$F exists, skipping"
+ continue;
+ fi
+ echo "{" > "$F"
+
+ echo " \"name\": \"$CMD\"," >> "$F"
+ echo " \"brief\": [ \"a sentence or two explaining what this command does\" ]," >> "$F"
+ echo " \"description\": [ \"See <xref linkend=\\\"cmd-$LINE\\\"/>\" ]," >> "$F"
+ echo " \"support\": [ \"undocumented\" ]," >> "$F"
+ echo " \"avail\": \"0.0.0\"," >> "$F"
+ echo " \"hook\": \"undocumented\"," >> "$F"
+ echo " \"access\": \"write\"," >> $F
+
+ echo " \"cmd-syntax\": [ \"Syntax of the command\" ]," >> "$F"
+ echo " \"cmd-comment\": [ \"Possibly some extra comments after the syntax.\" ]," >> "$F"
+
+ echo " \"resp-syntax\": [ \"Syntax of the response\" ]," >> "$F"
+ echo " \"resp-comment\": [ \"Optional extra comments after the response syntax.\" ]" >> "$F"
+ echo "}" >> "$F"
+
+ echo "$CMD generated."
+done
diff --git a/src/share/api/gss-tsig-get-all.json b/src/share/api/gss-tsig-get-all.json
new file mode 100644
index 0000000..56350a4
--- /dev/null
+++ b/src/share/api/gss-tsig-get-all.json
@@ -0,0 +1,53 @@
+{
+ "access": "read",
+ "avail": "2.0.0",
+ "brief": [ "This command lists GSS-TSIG servers and keys." ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"gss-tsig-get-all\"",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-gss-tsig-get-all\"/>" ],
+ "hook": "gss_tsig",
+ "name": "gss-tsig-get-all",
+ "support": [ "kea-dhcp-ddns" ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 GSS-TSIG servers and 1 keys\",",
+ " \"arguments\": {",
+ " \"gss-tsig-servers\": [",
+ " {",
+ " \"id\": \"foo\",",
+ " \"ip-address\": \"192.1.2.3\",",
+ " \"port\": 53,",
+ " \"server-principal\": \"DNS/foo.com@FOO.COM\",",
+ " \"key-name-suffix\": \"foo.com.\",",
+ " \"tkey-lifetime\": 3600,",
+ " \"tkey-protocol\": \"TCP\",",
+ " \"keys\": [",
+ " \"{",
+ " \"name\": \"1234.sig-foo.com.\",",
+ " \"inception-date\": \"2021-09-05 12:23:36.281176\",",
+ " \"server-id\": \"foo\",",
+ " \"expire-date\": \"2021-09-05 13:23:36.281176\",",
+ " \"status\": \"not yet ready\",",
+ " \"tkey-exchange\": true",
+ " }",
+ " ]",
+ " },",
+ " {",
+ " \"id\": \"bar\",",
+ " \"ip-address\": \"192.1.2.4\",",
+ " \"port\": 53,",
+ " \"server-principal\": \"DNS/bar.com@FOO.COM\",",
+ " \"key-name-suffix\": \"bar.com.\",",
+ " \"tkey-lifetime\": 7200,",
+ " \"tkey-protocol\": \"UDP\",",
+ " \"keys\": [ ]",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ]
+}
diff --git a/src/share/api/gss-tsig-get.json b/src/share/api/gss-tsig-get.json
new file mode 100644
index 0000000..3da4251
--- /dev/null
+++ b/src/share/api/gss-tsig-get.json
@@ -0,0 +1,44 @@
+{
+ "access": "read",
+ "avail": "2.0.0",
+ "brief": [
+ "This command retrieves information about the specified GSS-TSIG server."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"gss-tsig-get\",",
+ " \"arguments\": {",
+ " \"server-id\": \"foo\"",
+ " }",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-gss-tsig-get\"/>" ],
+ "hook": "gss_tsig",
+ "name": "gss-tsig-get",
+ "support": [ "kea-dhcp-ddns" ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"GSS-TSIG server[foo] found\",",
+ " \"arguments\": {",
+ " \"id\": \"foo\",",
+ " \"ip-address\": \"192.1.2.3\",",
+ " \"port\": 53,",
+ " \"server-principal\": \"DNS/foo.com@FOO.COM\",",
+ " \"key-name-suffix\": \"foo.com.\",",
+ " \"tkey-lifetime\": 3600,",
+ " \"tkey-protocol\": \"TCP\",",
+ " \"keys\": [",
+ " \"{",
+ " \"name\": \"1234.sig-foo.com.\",",
+ " \"server-id\": \"foo\",",
+ " \"inception-date\": \"2021-09-05 12:23:36.281176\",",
+ " \"expire-date\": \"2021-09-05 13:23:36.281176\",",
+ " \"status\": \"not yet ready\",",
+ " \"tkey-exchange\": true",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ]
+}
diff --git a/src/share/api/gss-tsig-key-del.json b/src/share/api/gss-tsig-key-del.json
new file mode 100644
index 0000000..115f56c
--- /dev/null
+++ b/src/share/api/gss-tsig-key-del.json
@@ -0,0 +1,23 @@
+{
+ "access": "write",
+ "avail": "2.0.0",
+ "brief": [ "This command deletes the specified GSS-TSIG key." ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"gss-tsig-key-del\",",
+ " \"arguments\": {",
+ " \"key-name\": \"1234.sig-foo.com.\"",
+ " }",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-gss-tsig-key-del\"/>" ],
+ "hook": "gss_tsig",
+ "name": "gss-tsig-key-del",
+ "support": [ "kea-dhcp-ddns" ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"GSS-TSIG key '1234.sig-foo.com.' deleted\"",
+ "}"
+ ]
+}
diff --git a/src/share/api/gss-tsig-key-expire.json b/src/share/api/gss-tsig-key-expire.json
new file mode 100644
index 0000000..7dff6e5
--- /dev/null
+++ b/src/share/api/gss-tsig-key-expire.json
@@ -0,0 +1,23 @@
+{
+ "access": "write",
+ "avail": "2.0.0",
+ "brief": [ "This command expires the specified GSS-TSIG key." ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"gss-tsig-key-expire\",",
+ " \"arguments\": {",
+ " \"key-name\": \"1234.sig-foo.com.\"",
+ " }",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-gss-tsig-key-expire\"/>" ],
+ "hook": "gss_tsig",
+ "name": "gss-tsig-key-expire",
+ "support": [ "kea-dhcp-ddns" ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"GSS-TSIG key '1234.sig-foo.com.' expired\"",
+ "}"
+ ]
+}
diff --git a/src/share/api/gss-tsig-key-get.json b/src/share/api/gss-tsig-key-get.json
new file mode 100644
index 0000000..4003b69
--- /dev/null
+++ b/src/share/api/gss-tsig-key-get.json
@@ -0,0 +1,33 @@
+{
+ "access": "read",
+ "avail": "2.0.0",
+ "brief": [
+ "This command retrieves information about the specified GSS-TSIG key."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"gss-tsig-key-get\",",
+ " \"arguments\": {",
+ " \"key-name\": \"1234.sig-foo.com.\"",
+ " }",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-gss-tsig-key-get\"/>" ],
+ "hook": "gss_tsig",
+ "name": "gss-tsig-key-get",
+ "support": [ "kea-dhcp-ddns" ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"GSS-TSIG key '1234.sig-foo.com.' found\",",
+ " \"arguments\": {",
+ " \"name\": \"1234.sig-foo.com.\",",
+ " \"server-id\": \"foo\",",
+ " \"inception-date\": \"2021-09-05 12:23:36.281176\",",
+ " \"expire-date\": \"2021-09-05 13:23:36.281176\",",
+ " \"status\": \"not yet ready\",",
+ " \"tkey-exchange\": true",
+ " }",
+ "}"
+ ]
+}
diff --git a/src/share/api/gss-tsig-list.json b/src/share/api/gss-tsig-list.json
new file mode 100644
index 0000000..c2f5946
--- /dev/null
+++ b/src/share/api/gss-tsig-list.json
@@ -0,0 +1,31 @@
+{
+ "access": "read",
+ "avail": "2.0.0",
+ "brief": [ "This command lists GSS-TSIG server IDs and key names." ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"gss-tsig-list\"",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-gss-tsig-list\"/>" ],
+ "hook": "gss_tsig",
+ "name": "gss-tsig-list",
+ "support": [ "kea-dhcp-ddns" ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 GSS-TSIG servers and 3 keys\",",
+ " \"arguments\": {",
+ " \"gss-tsig-servers\": [",
+ " \"foo\",",
+ " \"bar\"",
+ " ],",
+ " \"gss-tsig-keys\": [",
+ " \"1234.example.com.\",",
+ " \"5678.example.com.\",",
+ " \"43888.example.org.\"",
+ " ]",
+ " }",
+ "}"
+ ]
+}
diff --git a/src/share/api/gss-tsig-purge-all.json b/src/share/api/gss-tsig-purge-all.json
new file mode 100644
index 0000000..638b6fd
--- /dev/null
+++ b/src/share/api/gss-tsig-purge-all.json
@@ -0,0 +1,20 @@
+{
+ "access": "write",
+ "avail": "2.0.0",
+ "brief": [ "This command removes not usable GSS-TSIG keys." ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"gss-tsig-purge-all\"",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-gss-tsig-purge-all\"/>" ],
+ "hook": "gss_tsig",
+ "name": "gss-tsig-purge-all",
+ "support": [ "kea-dhcp-ddns" ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 purged GSS-TSIG keys\"",
+ "}"
+ ]
+}
diff --git a/src/share/api/gss-tsig-purge.json b/src/share/api/gss-tsig-purge.json
new file mode 100644
index 0000000..8b23086
--- /dev/null
+++ b/src/share/api/gss-tsig-purge.json
@@ -0,0 +1,24 @@
+{
+ "access": "write",
+ "avail": "2.0.0",
+ "brief": [ "This command removes not usable GSS-TSIG keys for the specified server."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"gss-tsig-purge\",",
+ " \"arguments\": {",
+ " \"server-id\": \"foo\"",
+ " }",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-gss-tsig-purge\"/>" ],
+ "hook": "gss_tsig",
+ "name": "gss-tsig-purge",
+ "support": [ "kea-dhcp-ddns" ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 purged keys for GSS-TSIG server[foo]\"",
+ "}"
+ ]
+}
diff --git a/src/share/api/gss-tsig-rekey-all.json b/src/share/api/gss-tsig-rekey-all.json
new file mode 100644
index 0000000..844fa85
--- /dev/null
+++ b/src/share/api/gss-tsig-rekey-all.json
@@ -0,0 +1,20 @@
+{
+ "access": "write",
+ "avail": "2.0.0",
+ "brief": [ "This command unconditionally creates new GSS-TSIG keys (rekeys) for all DNS servers." ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"gss-tsig-rekey-all\"",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-gss-tsig-rekey-all\"/>" ],
+ "hook": "gss_tsig",
+ "name": "gss-tsig-rekey-all",
+ "support": [ "kea-dhcp-ddns" ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"rekeyed\"",
+ "}"
+ ]
+}
diff --git a/src/share/api/gss-tsig-rekey.json b/src/share/api/gss-tsig-rekey.json
new file mode 100644
index 0000000..6198973
--- /dev/null
+++ b/src/share/api/gss-tsig-rekey.json
@@ -0,0 +1,24 @@
+{
+ "access": "write",
+ "avail": "2.0.0",
+ "brief": [ "The command unconditionally creates new GSS-TSIG keys for (rekeys) a specified DNS server."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"gss-tsig-rekey\",",
+ " \"arguments\": {",
+ " \"server-id\": \"foo\"",
+ " }",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-gss-tsig-rekey\"/>" ],
+ "hook": "gss_tsig",
+ "name": "gss-tsig-rekey",
+ "support": [ "kea-dhcp-ddns" ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"GSS-TSIG server[foo] rekeyed\"",
+ "}"
+ ]
+}
diff --git a/src/share/api/ha-continue.json b/src/share/api/ha-continue.json
new file mode 100644
index 0000000..606296c
--- /dev/null
+++ b/src/share/api/ha-continue.json
@@ -0,0 +1,14 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command resumes the operation of a paused HA state machine."
+ ],
+ "description": "See <xref linkend=\"command-ha-continue\"/>",
+ "hook": "high_availability",
+ "name": "ha-continue",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/ha-heartbeat.json b/src/share/api/ha-heartbeat.json
new file mode 100644
index 0000000..f7f0539
--- /dev/null
+++ b/src/share/api/ha-heartbeat.json
@@ -0,0 +1,34 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command is sent internally by a Kea partner when operating in High Availability (HA) mode or by the system administrator to verify the state of the servers with regards to the High Availability. It retrieves the server's HA state and clock value."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"ha-heartbeat\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"ha-server-states\"/>",
+ "hook": "high_availability",
+ "name": "ha-heartbeat",
+ "resp-comment": [
+ "The response includes a server state (see :ref:`ha-server-states`), current clock value, served scopes and the counter indicating how many leases the server has allocated without sending lease updates to its partner. The partner uses this counter to determine if it should synchronize its lease database."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"HA peer status returned.\",",
+ " \"arguments\": {",
+ " \"state\": <server state>,",
+ " \"date-time\": <server notion of time>,",
+ " \"scopes\": [ <first scope>, <second scope>, ... ],",
+ " \"unsent-update-count\": <total number of lease allocations in partner-down state>",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/ha-maintenance-cancel.json b/src/share/api/ha-maintenance-cancel.json
new file mode 100644
index 0000000..4ea868f
--- /dev/null
+++ b/src/share/api/ha-maintenance-cancel.json
@@ -0,0 +1,17 @@
+{
+ "access": "write",
+ "avail": "1.7.4",
+ "brief": [
+ "This command is sent to instruct a server in the partner-in-maintenance",
+ "state to transition back to the previous state, effectively canceling the",
+ "maintenance."
+ ],
+ "cmd-comment": [ "This command takes no arguments." ],
+ "description": [ "See <xref linkend=\"command-ha-maintenance-cancel\"/>" ],
+ "hook": "high_availability",
+ "name": "ha-maintenance-cancel",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/ha-maintenance-notify.json b/src/share/api/ha-maintenance-notify.json
new file mode 100644
index 0000000..d187107
--- /dev/null
+++ b/src/share/api/ha-maintenance-notify.json
@@ -0,0 +1,37 @@
+{
+ "access": "write",
+ "avail": "1.7.4",
+ "brief": [
+ "This command is sent by the server receiving the ha-maintenance-start",
+ "to its partner to cause the partner to transition to the in-maintenance",
+ "state or to revert it from the in-maintenance state as a result of",
+ "receiving the ha-maintenance-cancel command."
+ ],
+ "cmd-comment": [
+ "This command includes a boolean argument which, if false, indicates",
+ "that the server should transition to the in-maintenance state.",
+ "If the argument is set to true it instructs the server to revert from",
+ "the in-maintenance state to its previous state.",
+ "This command is not meant to be used by the administrator. It is",
+ "merely used for internal communication between the HA partners."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"ha-maintenance-notify\",",
+ " \"arguments\": {",
+ " \"cancel\": <boolean>",
+ " }",
+ "}"
+ ],
+ "description": [ "See <xref linkend=\"command-ha-maintenance-notify\"/>" ],
+ "hook": "high_availability",
+ "name": "ha-maintenance-notify",
+ "resp-comment": [
+ "The response may include a special error code of 1001",
+ "to indicate that the partner refused to enter the maintenance state."
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/ha-maintenance-start.json b/src/share/api/ha-maintenance-start.json
new file mode 100644
index 0000000..d76f2f0
--- /dev/null
+++ b/src/share/api/ha-maintenance-start.json
@@ -0,0 +1,19 @@
+{
+ "access": "write",
+ "avail": "1.7.4",
+ "brief": [
+ "This command is sent to instruct one of the servers to transition",
+ "to the partner-in-maintenance state in which it will be responding to",
+ "all DHCP queries. The server receiving this command sends the",
+ "ha-maintenance-notify to its partner to cause the partner to",
+ "transition to the in-maintenance state."
+ ],
+ "cmd-comment": [ "This command takes no arguments." ],
+ "description": [ "See <xref linkend=\"command-ha-maintenance-start\"/>" ],
+ "hook": "high_availability",
+ "name": "ha-maintenance-start",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/ha-reset.json b/src/share/api/ha-reset.json
new file mode 100644
index 0000000..6a78a12
--- /dev/null
+++ b/src/share/api/ha-reset.json
@@ -0,0 +1,14 @@
+{
+ "access": "write",
+ "avail": "1.9.4",
+ "brief": [
+ "This command resets the HA state machine of the server by transitioning it to the waiting state."
+ ],
+ "description": "See <xref linkend=\"command-ha-reset\"/>",
+ "hook": "high_availability",
+ "name": "ha-reset",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/ha-scopes.json b/src/share/api/ha-scopes.json
new file mode 100644
index 0000000..99240aa
--- /dev/null
+++ b/src/share/api/ha-scopes.json
@@ -0,0 +1,26 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command modifies the scope that the server is responsible for serving when operating in High Availability (HA) mode."
+ ],
+ "cmd-comment": [
+ "In the example below, the arguments configure the server to handle traffic from both the HA_server1 and HA_server2 scopes."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"ha-scopes\",",
+ " \"service\": [ <service, typically 'dhcp4' or 'dhcp6'> ],",
+ " \"arguments\": {",
+ " \"scopes\": [ \"HA_server1\", \"HA_server2\" ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-ha-scopes\"/>",
+ "hook": "high_availability",
+ "name": "ha-scopes",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/ha-sync-complete-notify.json b/src/share/api/ha-sync-complete-notify.json
new file mode 100644
index 0000000..998157b
--- /dev/null
+++ b/src/share/api/ha-sync-complete-notify.json
@@ -0,0 +1,19 @@
+{
+ "access": "write",
+ "avail": "1.9.11",
+ "brief": [
+ "A server notifies its partner with this command that it has finished the",
+ "lease database synchronization. If the partner is in the partner-down state",
+ "it temporarily stops allocating new leases until it transitions to a normal",
+ "operation state (e.g. load-balancing). If the partner observes a failing",
+ "heartbeat it can resume allocating new leases in the partner-down state."
+ ],
+ "cmd-comment": [ "This command takes no arguments." ],
+ "description": [ "See <xref linkend=\"command-ha-sync-complete-notify\"/>" ],
+ "hook": "high_availability",
+ "name": "ha-sync-complete-notify",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/ha-sync.json b/src/share/api/ha-sync.json
new file mode 100644
index 0000000..b4e4277
--- /dev/null
+++ b/src/share/api/ha-sync.json
@@ -0,0 +1,24 @@
+{
+ "access": "write",
+ "avail": "1.4.0",
+ "brief": [
+ "This command instructs the server running in HA mode to synchronize its local lease database with the selected peer."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"ha-sync\",",
+ " \"service\": [ <service affected: 'dhcp4' or 'dhcp6'> ],",
+ " \"arguments\": {",
+ " \"server-name\": <name of the partner server>,",
+ " \"max-period\": <integer, in seconds>",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-ha-sync\"/>",
+ "hook": "high_availability",
+ "name": "ha-sync",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease4-add.json b/src/share/api/lease4-add.json
new file mode 100644
index 0000000..c7fbca2
--- /dev/null
+++ b/src/share/api/lease4-add.json
@@ -0,0 +1,25 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command administratively adds a new IPv4 lease."
+ ],
+ "cmd-comment": [
+ "Note that Kea 1.4 requires an additional argument, subnet-ID, which is optional as of Kea 1.5. A number of other, more-detailed, optional arguments are also supported."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-add\",",
+ " \"arguments\": {",
+ " \"ip-address\": \"192.0.2.202\",",
+ " \"hw-address\": \"1a:1b:1c:1d:1e:1f\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp64\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-add",
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease4-del.json b/src/share/api/lease4-del.json
new file mode 100644
index 0000000..49719b2
--- /dev/null
+++ b/src/share/api/lease4-del.json
@@ -0,0 +1,24 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command deletes a lease from the lease database."
+ ],
+ "cmd-comment": [
+ "The lease to be deleted can be specified either by IP address or by identifier-type and identifier value. The currently supported identifiers are \"hw-address\" and \"client-id\"."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-del\",",
+ " \"arguments\": {",
+ " \"ip-address\": \"192.0.2.202\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-lease4-del\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-del",
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease4-get-all.json b/src/share/api/lease4-get-all.json
new file mode 100644
index 0000000..498af13
--- /dev/null
+++ b/src/share/api/lease4-get-all.json
@@ -0,0 +1,66 @@
+{
+ "access": "read",
+ "avail": "1.4.0",
+ "brief": [
+ "This command retrieves all IPv4 leases or all leases for the specified set of subnets."
+ ],
+ "cmd-comment": [
+ "The ``lease4-get-all`` command may result in very large responses. Please consider using ",
+ "``lease4-get-page`` to get them in chunks. The ``subnets`` parameter is optional. If not ",
+ "specified, leases from all subnets are returned."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-get-all\",",
+ " \"arguments\": {",
+ " \"subnets\": [ 1, 2, 3, ... ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp59\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-get-all",
+ "resp-comment": [
+ "Result 0 is returned when at least one lease is found, 1 when parameters are malformed or missing,",
+ "3 is returned if no leases are found with specified parameter."
+ ],
+ "resp-syntax": [
+ "[",
+ " {",
+ " \"arguments\": {",
+ " \"leases\": [",
+ " {",
+ " \"client-id\": \"01:00:0c:01:02:03:04\",",
+ " \"cltt\": 1600432232,",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"\",",
+ " \"hw-address\": \"00:0c:01:02:03:04\",",
+ " \"ip-address\": \"192.168.1.150\",",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"valid-lft\": 4000",
+ " },",
+ " {",
+ " \"client-id\": \"01:00:0c:01:02:03:05\",",
+ " \"cltt\": 1600432234,",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"\",",
+ " \"hw-address\": \"00:0c:01:02:03:05\",",
+ " \"ip-address\": \"192.168.1.151\",",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"valid-lft\": 4000",
+ " }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv4 lease(s) found.\"",
+ " }",
+ "]"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease4-get-by-client-id.json b/src/share/api/lease4-get-by-client-id.json
new file mode 100644
index 0000000..9e2aec7
--- /dev/null
+++ b/src/share/api/lease4-get-by-client-id.json
@@ -0,0 +1,49 @@
+{
+ "access": "read",
+ "avail": "1.7.1",
+ "brief": [
+ "This command retrieves all IPv4 leases with the specified client id."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-get-by-client-id\",",
+ " \"arguments\": {",
+ " \"client-id\": \"01:00:0c:01:02:03:04\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-lease4-get-by-client-id\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-get-by-client-id",
+ "resp-comment": [
+ "Result 0 is returned when at least one lease is found, 1 when parameters are malformed or missing,",
+ "3 is returned if no leases are found."
+ ],
+ "resp-syntax": [
+ "[",
+ " {",
+ " \"arguments\": {",
+ " \"leases\": [",
+ " {",
+ " \"client-id\": \"01:00:0c:01:02:03:04\",",
+ " \"cltt\": 1600432232,",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"\",",
+ " \"hw-address\": \"00:0c:01:02:03:04\",",
+ " \"ip-address\": \"192.168.1.150\",",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"valid-lft\": 4000",
+ " },",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv4 lease(s) found.\"",
+ " }",
+ "]"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease4-get-by-hostname.json b/src/share/api/lease4-get-by-hostname.json
new file mode 100644
index 0000000..9ce4b42
--- /dev/null
+++ b/src/share/api/lease4-get-by-hostname.json
@@ -0,0 +1,49 @@
+{
+ "access": "read",
+ "avail": "1.7.1",
+ "brief": [
+ "This command retrieves all IPv4 leases with the specified hostname."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-get-by-hostname\",",
+ " \"arguments\": {",
+ " \"hostname\": \"myhost.example.com.\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-lease4-get-by-hostname\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-get-by-hostname",
+ "resp-comment": [
+ "Result 0 is returned when at least one lease is found, 1 when parameters are malformed or missing,",
+ "3 is returned if no leases are found."
+ ],
+ "resp-syntax": [
+ "[",
+ " {",
+ " \"arguments\": {",
+ " \"leases\": [",
+ " {",
+ " \"client-id\": \"01:00:0c:01:02:03:04\",",
+ " \"cltt\": 1600432232,",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"myhost.example.com.\",",
+ " \"hw-address\": \"00:0c:01:02:03:04\",",
+ " \"ip-address\": \"192.168.1.150\",",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"valid-lft\": 4000",
+ " },",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv4 lease(s) found.\"",
+ " }",
+ "]"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease4-get-by-hw-address.json b/src/share/api/lease4-get-by-hw-address.json
new file mode 100644
index 0000000..19c66c1
--- /dev/null
+++ b/src/share/api/lease4-get-by-hw-address.json
@@ -0,0 +1,50 @@
+{
+ "access": "read",
+ "avail": "1.7.1",
+ "brief": [
+ "This command retrieves all IPv4 leases with the specified hardware address."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-get-by-hw-address\",",
+ " \"arguments\": {",
+ " \"hw-address\": \"00:0c:01:02:03:04\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-lease4-get-by-hw-address\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-get-by-hw-address",
+ "resp-comment": [
+ "Result 0 is returned when at least one lease is found, 1 when parameters are malformed or missing,",
+ "3 is returned if no leases are found."
+ ],
+ "resp-syntax": [
+ "[",
+ " {",
+ " \"arguments\": {",
+ " \"leases\": [",
+ " {",
+ " \"client-id\": \"01:00:0c:01:02:03:04\",",
+ " \"cltt\": 1600432232,",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"myhost.example.com.\",",
+ " \"hw-address\": \"00:0c:01:02:03:04\",",
+ " \"ip-address\": \"192.168.1.150\",",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"valid-lft\": 4000",
+ " },",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv4 lease(s) found.\"",
+ " }",
+ "]"
+ ],
+
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease4-get-page.json b/src/share/api/lease4-get-page.json
new file mode 100644
index 0000000..266b4a9
--- /dev/null
+++ b/src/share/api/lease4-get-page.json
@@ -0,0 +1,65 @@
+{
+ "access": "read",
+ "avail": "1.5.0",
+ "brief": [
+ "This command retrieves all IPv4 leases by page."
+ ],
+ "cmd-comment": [
+ "The from address and the page size limit are mandatory."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-get-page\",",
+ " \"arguments\": {",
+ " \"limit\": <integer>,",
+ " \"from\": <IPv4 address or \"start\">",
+ " }",
+ "}"
+ ],
+ "resp-comment": [
+ "Result 0 is returned when at least one lease is found, 1 when parameters are malformed or missing,",
+ "3 is returned if no leases are found with specified parameters."
+ ],
+ "resp-syntax": [
+ "[",
+ " {",
+ " \"arguments\": {",
+ " \"leases\": [",
+ " {",
+ " \"client-id\": \"01:00:0c:01:02:03:04\",",
+ " \"cltt\": 1600432232,",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"\",",
+ " \"hw-address\": \"00:0c:01:02:03:04\",",
+ " \"ip-address\": \"192.168.1.150\",",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"valid-lft\": 4000",
+ " },",
+ " {",
+ " \"client-id\": \"01:00:0c:01:02:03:05\",",
+ " \"cltt\": 1600432234,",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"\",",
+ " \"hw-address\": \"00:0c:01:02:03:05\",",
+ " \"ip-address\": \"192.168.1.151\",",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"valid-lft\": 4000",
+ " }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv4 lease(s) found.\"",
+ " }",
+ "]"
+ ],
+ "description": "See <xref linkend=\"command-lease4-get-page\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-get-page",
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease4-get.json b/src/share/api/lease4-get.json
new file mode 100644
index 0000000..3b76a10
--- /dev/null
+++ b/src/share/api/lease4-get.json
@@ -0,0 +1,44 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command queries the lease database and retrieves existing leases."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-get\",",
+ " \"arguments\": {",
+ " \"ip-address\": \"192.0.2.1\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp58\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-get",
+ "resp-comment": [
+ "lease4-get returns a result that indicates the outcome of the operation and lease details,",
+ "if found. It has one of the following values: 0 (success), 1 (error), or 3 (empty). ",
+ "Result 3 is returned if no leases are found with specified IP address."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"arguments\": {",
+ " \"client-id\": \"42:42:42:42:42:42:42:42\",",
+ " \"cltt\": 12345678,",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": true,",
+ " \"hostname\": \"myhost.example.com.\",",
+ " \"hw-address\": \"08:08:08:08:08:08\",",
+ " \"ip-address\": \"192.0.2.1\",",
+ " \"state\": 0,",
+ " \"subnet-id\": 44,",
+ " \"valid-lft\": 3600",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 lease found.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease4-resend-ddns.json b/src/share/api/lease4-resend-ddns.json
new file mode 100644
index 0000000..a6c9014
--- /dev/null
+++ b/src/share/api/lease4-resend-ddns.json
@@ -0,0 +1,32 @@
+{
+ "access": "write",
+ "avail": "1.7.6",
+ "brief": [
+ "This command resends a request to kea-dhcp-ddns to update DNS for an existing lease."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-resend-ddns\",",
+ " \"arguments\": {",
+ " \"ip-address\": \"192.0.2.1\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp58\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-resend-ddns",
+ "resp-comment": [
+ "lease4-resend-ddns returns a result that indicates the outcome of the operation and lease details, if found. It has one of the following values: 0 (success), 1 (error), or 3 (empty)."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"arguments\": {",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"NCR generated for: 192.0.2.1, hostname: example.com.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease4-update.json b/src/share/api/lease4-update.json
new file mode 100644
index 0000000..4cb65fa
--- /dev/null
+++ b/src/share/api/lease4-update.json
@@ -0,0 +1,25 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command updates existing leases."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-update\",",
+ " \"arguments\": {",
+ " \"ip-address\": \"192.0.2.1\",",
+ " \"hostname\": \"newhostname.example.org\",",
+ " \"hw-address\": \"1a:1b:1c:1d:1e:1f\",",
+ " \"subnet-id\": 44,",
+ " \"force-create\": true",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp62\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-update",
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease4-wipe.json b/src/share/api/lease4-wipe.json
new file mode 100644
index 0000000..5ee4fcc
--- /dev/null
+++ b/src/share/api/lease4-wipe.json
@@ -0,0 +1,21 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command removes all leases associated with a given subnet."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease4-wipe\",",
+ " \"arguments\": {",
+ " \"subnet-id\": 44",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp63\"/>",
+ "hook": "lease_cmds",
+ "name": "lease4-wipe",
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/lease6-add.json b/src/share/api/lease6-add.json
new file mode 100644
index 0000000..5c4ba5a
--- /dev/null
+++ b/src/share/api/lease6-add.json
@@ -0,0 +1,32 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command administratively creates a new lease."
+ ],
+ "cmd-comment": [
+ "lease6-add can be also used to add leases for IPv6 prefixes."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-add\",",
+ " \"arguments\": {",
+ " \"subnet-id\": 66,",
+ " \"ip-address\": \"2001:db8::3\",",
+ " \"duid\": \"1a:1b:1c:1d:1e:1f:20:21:22:23:24\",",
+ " \"iaid\": 1234",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp64\"/>",
+ "hook": "lease_cmds",
+ "name": "lease6-add",
+ "resp-syntax": [
+ "{ \"result\": 0, \"text\": \"Lease added.\" }",
+ "or",
+ "{ \"result\": 1, \"text\": \"missing parameter 'ip-address' (<string>:3:19)\" }"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease6-bulk-apply.json b/src/share/api/lease6-bulk-apply.json
new file mode 100644
index 0000000..2958e41
--- /dev/null
+++ b/src/share/api/lease6-bulk-apply.json
@@ -0,0 +1,75 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates, updates, or deletes multiple IPv6 leases in a single transaction. It communicates lease changes between HA peers, but may be used in all cases where it is desirable to apply multiple lease updates in a single transaction."
+ ],
+ "cmd-comment": [
+ "If any of the leases is malformed, all changes are rolled back. If the leases are well-formed but the operation fails for one or more leases, these leases are listed in the response; however, the changes are preserved for all leases for which the operation was successful. The \"deleted-leases\" and \"leases\" are optional parameters, but one of them must be specified."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-bulk-apply\",",
+ " \"arguments\": {",
+ " \"deleted-leases\": [",
+ " {",
+ " \"ip-address\": \"2001:db8:abcd::\",",
+ " \"type\": \"IA_PD\",",
+ " ...",
+ " },",
+ " {",
+ " \"ip-address\": \"2001:db8:abcd::234\",",
+ " \"type\": \"IA_NA\",",
+ " ...",
+ " }",
+ " ],",
+ " \"leases\": [",
+ " {",
+ " \"subnet-id\": 66,",
+ " \"ip-address\": \"2001:db8:cafe::\",",
+ " \"type\": \"IA_PD\",",
+ " ...",
+ " },",
+ " {",
+ " \"subnet-id\": 66,",
+ " \"ip-address\": \"2001:db8:abcd::333\",",
+ " \"type\": \"IA_NA\",",
+ " ...",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "hook": "lease_cmds",
+ "name": "lease6-bulk-apply",
+ "resp-comment": [
+ "The \"failed-deleted-leases\" holds the list of leases which failed to delete; this includes leases which were not found in the database. The \"failed-leases\" includes the list of leases which failed to create or update. For each lease for which there was an error during processing, insertion into the database, etc., the result is set to 1. For each lease which was not deleted because the server did not find it in the database, the result of 3 is returned."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 leases bulk apply completed.\",",
+ " \"arguments\": {",
+ " \"failed-deleted-leases\": [",
+ " {",
+ " \"ip-address\": \"2001:db8:abcd::\",",
+ " \"type\": \"IA_PD\",",
+ " \"result\": <control result>,",
+ " \"error-message\": <error message>",
+ " }",
+ " ],",
+ " \"failed-leases\": [",
+ " {",
+ " \"ip-address\": \"2001:db8:cafe::\",",
+ " \"type\": \"IA_PD\",",
+ " \"result\": <control result>,",
+ " \"error-message\": <error message>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease6-del.json b/src/share/api/lease6-del.json
new file mode 100644
index 0000000..8164aa5
--- /dev/null
+++ b/src/share/api/lease6-del.json
@@ -0,0 +1,24 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command deletes a lease from the lease database."
+ ],
+ "cmd-comment": [
+ "lease6-del returns a result that indicates the outcome of the operation. It has one of the following values: 0 (success), 1 (error), or 3 (empty)."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-del\",",
+ " \"arguments\": {",
+ " \"ip-address\": \"2001:db8::3\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"lease6-del\"/>",
+ "hook": "lease_cmds",
+ "name": "lease6-del",
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease6-get-all.json b/src/share/api/lease6-get-all.json
new file mode 100644
index 0000000..81a9f76
--- /dev/null
+++ b/src/share/api/lease6-get-all.json
@@ -0,0 +1,71 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command retrieves all IPv6 leases or all leases for the specified set of subnets."
+ ],
+ "cmd-comment": [
+ "The ``lease6-get-all`` command may result in very large responses. Please consider using ",
+ "``lease6-get-page`` to get them in chunks. the ``subnets`` parameter is optional. If not ",
+ "specified, leases from all subnets are returned."
+ ],
+
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-get-all\",",
+ " \"arguments\": {",
+ " \"subnets\": [ 1, 2, 3, 4 ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp59\"/>",
+ "hook": "lease_cmds",
+ "name": "lease6-get-all",
+ "resp-comment": [
+ "Result 0 is returned when at least one lease is found, 1 when parameters are malformed or missing,",
+ "3 is returned if no leases are found with specified parameter."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"arguments\": {",
+ " \"leases\": [",
+ " {",
+ " \"cltt\": 12345678,",
+ " \"duid\": \"42:42:42:42:42:42:42:42\",",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": true,",
+ " \"hostname\": \"myhost.example.com.\",",
+ " \"hw-address\": \"08:08:08:08:08:08\",",
+ " \"iaid\": 1,",
+ " \"ip-address\": \"2001:db8:2::1\",",
+ " \"preferred-lft\": 500,",
+ " \"state\": 0,",
+ " \"subnet-id\": 44,",
+ " \"type\": \"IA_NA\",",
+ " \"valid-lft\": 3600",
+ " },",
+ " {",
+ " \"cltt\": 12345678,",
+ " \"duid\": \"21:21:21:21:21:21:21:21\",",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": true,",
+ " \"hostname\": \"\",",
+ " \"iaid\": 1,",
+ " \"ip-address\": \"2001:db8:0:0:2::\",",
+ " \"preferred-lft\": 500,",
+ " \"prefix-len\": 80,",
+ " \"state\": 0,",
+ " \"subnet-id\": 44,",
+ " \"type\": \"IA_PD\",",
+ " \"valid-lft\": 3600",
+ " }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv6 lease(s) found.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease6-get-by-duid.json b/src/share/api/lease6-get-by-duid.json
new file mode 100644
index 0000000..2c2fc1a
--- /dev/null
+++ b/src/share/api/lease6-get-by-duid.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.7.1",
+ "brief": [
+ "This command retrieves all IPv6 leases with the specified hardware address."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-get-by-duid\",",
+ " \"arguments\": {",
+ " \"duid\": \"1a:1b:1c:1d:1e:1f:20:21:22:23:24\"",
+ " }",
+ "}"
+ ],
+ "resp-comment": [
+ "Result 0 is returned when at least one lease is found, 1 when parameters are malformed or missing,",
+ "3 is returned if no leases are found with specified parameter."
+ ],
+ "resp-syntax": [
+ "[",
+ " {",
+ " \"arguments\": {",
+ " \"leases\": [",
+ " {",
+ " \"cltt\": 1600439560,",
+ " \"duid\": \"00:01:00:01:26:f7:81:88:00:0c:01:02:03:04\",",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"foobar.example.org\",",
+ " \"hw-address\": \"00:0c:01:02:03:04\",",
+ " \"iaid\": 1,",
+ " \"ip-address\": \"2001:db8:1::\",",
+ " \"preferred-lft\": 3000,",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"type\": \"IA_NA\",",
+ " \"valid-lft\": 4000",
+ " }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv6 lease(s) found.\"",
+ " }",
+ "]"
+ ],
+ "description": "See <xref linkend=\"command-lease6-get-by-duid\"/>",
+ "hook": "lease_cmds",
+ "name": "lease6-get-by-duid",
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease6-get-by-hostname.json b/src/share/api/lease6-get-by-hostname.json
new file mode 100644
index 0000000..d92af19
--- /dev/null
+++ b/src/share/api/lease6-get-by-hostname.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.7.1",
+ "brief": [
+ "This command retrieves all IPv6 leases with the specified hostname."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-get-by-hostname\",",
+ " \"arguments\": {",
+ " \"hostname\": \"myhost.example.com.\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-lease6-get-by-hostname\"/>",
+ "hook": "lease_cmds",
+ "name": "lease6-get-by-hostname",
+ "resp-comment": [
+ "Result 0 is returned when at least one lease is found, 1 when parameters are malformed or missing,",
+ "3 is returned if no leases are found with specified parameter."
+ ],
+ "resp-syntax": [
+ "[",
+ " {",
+ " \"arguments\": {",
+ " \"leases\": [",
+ " {",
+ " \"cltt\": 1600439560,",
+ " \"duid\": \"00:01:00:01:26:f7:81:88:00:0c:01:02:03:04\",",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"foobar.example.org\",",
+ " \"hw-address\": \"00:0c:01:02:03:04\",",
+ " \"iaid\": 1,",
+ " \"ip-address\": \"2001:db8:1::\",",
+ " \"preferred-lft\": 3000,",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"type\": \"IA_NA\",",
+ " \"valid-lft\": 4000",
+ " }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv6 lease(s) found.\"",
+ " }",
+ "]"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease6-get-page.json b/src/share/api/lease6-get-page.json
new file mode 100644
index 0000000..b45b650
--- /dev/null
+++ b/src/share/api/lease6-get-page.json
@@ -0,0 +1,70 @@
+{
+ "access": "read",
+ "avail": "1.5.0",
+ "brief": [
+ "This command retrieves all IPv6 leases by page."
+ ],
+ "cmd-comment": [
+ "The from address and the page size limit are mandatory."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-get-page\",",
+ " \"arguments\": {",
+ " \"limit\": <integer>,",
+ " \"from\": <IPv6 address or \"start\">",
+ " }",
+ "}"
+ ],
+ "resp-comment": [
+ "Result 0 is returned when at least one lease is found, 1 when parameters are malformed or missing,",
+ "3 is returned if no leases are found."
+ ],
+ "resp-syntax": [
+ "[",
+ " {",
+ " \"arguments\": {",
+ " \"leases\": [",
+ " {",
+ " \"cltt\": 1600439560,",
+ " \"duid\": \"00:01:00:01:26:f7:81:88:00:0c:01:02:03:04\",",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"foo.example.org\",",
+ " \"hw-address\": \"00:0c:01:02:03:04\",",
+ " \"iaid\": 1,",
+ " \"ip-address\": \"2001:db8:1::1\",",
+ " \"preferred-lft\": 3000,",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"type\": \"IA_NA\",",
+ " \"valid-lft\": 4000",
+ " }",
+ " {",
+ " \"cltt\": 1600439570,",
+ " \"duid\": \"00:01:00:01:26:f7:81:88:00:0c:01:02:03:05\",",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"bar.example.org\",",
+ " \"hw-address\": \"00:0c:01:02:03:05\",",
+ " \"iaid\": 1,",
+ " \"ip-address\": \"2001:db8:1::2\",",
+ " \"preferred-lft\": 3000,",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"type\": \"IA_NA\",",
+ " \"valid-lft\": 4000",
+ " }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv6 lease(s) found.\"",
+ "}",
+ "]"
+ ],
+ "hook": "lease_cmds",
+ "name": "lease6-get-page",
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease6-get.json b/src/share/api/lease6-get.json
new file mode 100644
index 0000000..e49dc53
--- /dev/null
+++ b/src/share/api/lease6-get.json
@@ -0,0 +1,56 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command queries the lease database and retrieves existing leases."
+ ],
+ "cmd-comment": [
+ "lease6-get returns a result that indicates the outcome of the operation and lease details, if found."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-get\",",
+ " \"arguments\": {",
+ " \"ip-address\": \"2001:db8:1234:ab::\",",
+ " \"type\": \"IA_PD\"",
+ " }",
+ "}"
+ ],
+ "resp-comment": [
+ "Result 0 is returned when at least one lease is found, 1 when parameters are malformed or missing,",
+ "3 is returned if no leases are found with specified parameter."
+ ],
+ "resp-syntax": [
+ "[",
+ " {",
+ " \"arguments\": {",
+ " \"leases\": [",
+ " {",
+ " \"cltt\": 1600439560,",
+ " \"duid\": \"00:01:00:01:26:f7:81:88:00:0c:01:02:03:04\",",
+ " \"fqdn-fwd\": false,",
+ " \"fqdn-rev\": false,",
+ " \"hostname\": \"foobar.example.org\",",
+ " \"hw-address\": \"00:0c:01:02:03:04\",",
+ " \"iaid\": 1,",
+ " \"ip-address\": \"2001:db8:1::\",",
+ " \"preferred-lft\": 3000,",
+ " \"state\": 0,",
+ " \"subnet-id\": 1,",
+ " \"type\": \"IA_NA\",",
+ " \"valid-lft\": 4000",
+ " }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv6 lease(s) found.\"",
+ " }",
+ "]"
+ ],
+ "description": "See <xref linkend=\"idp58\"/>",
+ "hook": "lease_cmds",
+ "name": "lease6-get",
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease6-resend-ddns.json b/src/share/api/lease6-resend-ddns.json
new file mode 100644
index 0000000..a8c21de
--- /dev/null
+++ b/src/share/api/lease6-resend-ddns.json
@@ -0,0 +1,32 @@
+{
+ "access": "write",
+ "avail": "1.7.6",
+ "brief": [
+ "This command resends a request to kea-dhcp-ddns to update DNS for an existing lease."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-resend-ddns\",",
+ " \"arguments\": {",
+ " \"ip-address\": \"2001:db8::1\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp58\"/>",
+ "hook": "lease_cmds",
+ "name": "lease6-resend-ddns",
+ "resp-comment": [
+ "lease6-resend-ddns returns a result that indicates the outcome of the operation and lease details, if found. It has one of the following values: 0 (success), 1 (error), or 3 (empty)."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"arguments\": {",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"NCR generated for: 2001:db8::1, hostname: example.com.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease6-update.json b/src/share/api/lease6-update.json
new file mode 100644
index 0000000..b0c871c
--- /dev/null
+++ b/src/share/api/lease6-update.json
@@ -0,0 +1,27 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command updates existing leases."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-update\",",
+ " \"arguments\": {",
+ " \"ip-address\": \"2001:db8::1\",",
+ " \"duid\": \"88:88:88:88:88:88:88:88\",",
+ " \"iaid\": 7654321,",
+ " \"hostname\": \"newhostname.example.org\",",
+ " \"subnet-id\": 66,",
+ " \"force-create\": false",
+ " }",
+ "}",
+ ""
+ ],
+ "description": "See <xref linkend=\"idp62\"/>",
+ "hook": "lease_cmds",
+ "name": "lease6-update",
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/lease6-wipe.json b/src/share/api/lease6-wipe.json
new file mode 100644
index 0000000..eb6f55a
--- /dev/null
+++ b/src/share/api/lease6-wipe.json
@@ -0,0 +1,24 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command removes all leases associated with a given subnet."
+ ],
+ "cmd-comment": [
+ "Note: not all backends support this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"lease6-wipe\",",
+ " \"arguments\": {",
+ " \"subnet-id\": 66",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp63\"/>",
+ "hook": "lease_cmds",
+ "name": "lease6-wipe",
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/leases-reclaim.json b/src/share/api/leases-reclaim.json
new file mode 100644
index 0000000..a270682
--- /dev/null
+++ b/src/share/api/leases-reclaim.json
@@ -0,0 +1,21 @@
+{
+ "access": "write",
+ "avail": "1.0.0",
+ "brief": [
+ "This command instructs the server to reclaim all expired leases immediately."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"leases-reclaim\",",
+ " \"arguments\": {",
+ " \"remove\": true",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-leases-reclaim\"/>",
+ "name": "leases-reclaim",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/libreload.json b/src/share/api/libreload.json
new file mode 100644
index 0000000..a487abc
--- /dev/null
+++ b/src/share/api/libreload.json
@@ -0,0 +1,22 @@
+{
+ "access": "write",
+ "avail": "1.2.0",
+ "brief": [
+ "This command first unloads and then reloads all currently loaded hooks libraries."
+ ],
+ "cmd-comment": [
+ "The server responds with 0, indicating success, or 1, indicating a failure."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"libreload\",",
+ " \"arguments\": { }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-libreload\"/>",
+ "name": "libreload",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/list-commands.json b/src/share/api/list-commands.json
new file mode 100644
index 0000000..25fd028
--- /dev/null
+++ b/src/share/api/list-commands.json
@@ -0,0 +1,24 @@
+{
+ "access": "read",
+ "avail": "1.0.0",
+ "brief": [
+ "This command retrieves a list of all commands supported by the server."
+ ],
+ "cmd-comment": [
+ "The server responds with a list of all supported commands."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"list-commands\",",
+ " \"arguments\": { }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-list-commands\"/>",
+ "name": "list-commands",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns",
+ "kea-ctrl-agent"
+ ]
+}
diff --git a/src/share/api/network4-add.json b/src/share/api/network4-add.json
new file mode 100644
index 0000000..623bb58
--- /dev/null
+++ b/src/share/api/network4-add.json
@@ -0,0 +1,55 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command adds a new shared network."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network4-add\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [ {",
+ " \"name\": \"floor13\",",
+ " \"subnet4\": [",
+ " {",
+ " \"id\": 100,",
+ " \"pools\": [ { \"pool\": \"192.0.2.2-192.0.2.99\" } ],",
+ " \"subnet\": \"192.0.2.0/24\",",
+ " \"option-data\": [",
+ " {",
+ " \"name\": \"routers\",",
+ " \"data\": \"192.0.2.1\"",
+ " }",
+ " ]",
+ " },",
+ " {",
+ " \"id\": 101,",
+ " \"pools\": [ { \"pool\": \"192.0.3.2-192.0.3.99\" } ],",
+ " \"subnet\": \"192.0.3.0/24\",",
+ " \"option-data\": [",
+ " {",
+ " \"name\": \"routers\",",
+ " \"data\": \"192.0.3.1\"",
+ " }",
+ " ]",
+ " } ]",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp75\"/>",
+ "hook": "subnet_cmds",
+ "name": "network4-add",
+ "resp-syntax": [
+ "{",
+ " \"arguments\": {",
+ " \"shared-networks\": [ { \"name\": \"floor13\" } ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"A new IPv4 shared network 'floor13' added\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/network4-del.json b/src/share/api/network4-del.json
new file mode 100644
index 0000000..a11feae
--- /dev/null
+++ b/src/share/api/network4-del.json
@@ -0,0 +1,35 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command deletes existing shared networks."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network4-del\",",
+ " \"arguments\": {",
+ " \"name\": \"floor13\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-network4-del\"/>",
+ "hook": "subnet_cmds",
+ "name": "network4-del",
+ "resp-syntax": [
+ "{",
+ " \"command\": \"network4-del\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": \"floor13\"",
+ " }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 shared network 'floor13' deleted\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/network4-get.json b/src/share/api/network4-get.json
new file mode 100644
index 0000000..41f3844
--- /dev/null
+++ b/src/share/api/network4-get.json
@@ -0,0 +1,57 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command retrieves detailed information about shared networks, including subnets that are currently part of a given network."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network4-get\",",
+ " \"arguments\": {",
+ " \"name\": \"floor13\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp74\"/>",
+ "hook": "subnet_cmds",
+ "name": "network4-get",
+ "resp-comment": [
+ "Note that the actual response contains many additional fields that are omitted here for clarity."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"Info about IPv4 shared network 'floor13' returned\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"match-client-id\": true,",
+ " \"name\": \"floor13\",",
+ " \"option-data\": [ ],",
+ " \"rebind-timer\": 90,",
+ " \"relay\": {",
+ " \"ip-address\": \"0.0.0.0\"",
+ " },",
+ " \"renew-timer\": 60,",
+ " \"subnet4\": [",
+ " {",
+ " \"subnet\": \"192.0.2.0/24\",",
+ " \"id\": 5,",
+ " // many other subnet specific details here",
+ " },",
+ " {",
+ " \"subnet\": \"192.0.3.0/31\",",
+ " \"id\": 6,",
+ " // many other subnet specific details here",
+ " }",
+ " ],",
+ " \"valid-lifetime\": 120",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/network4-list.json b/src/share/api/network4-list.json
new file mode 100644
index 0000000..459f089
--- /dev/null
+++ b/src/share/api/network4-list.json
@@ -0,0 +1,30 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command retrieves the full list of currently configured shared networks."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network4-list\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-network4-list\"/>",
+ "hook": "subnet_cmds",
+ "name": "network4-list",
+ "resp-syntax": [
+ "{",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " { \"name\": \"floor1\" },",
+ " { \"name\": \"office\" }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv4 network(s) found\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/network4-subnet-add.json b/src/share/api/network4-subnet-add.json
new file mode 100644
index 0000000..edb6257
--- /dev/null
+++ b/src/share/api/network4-subnet-add.json
@@ -0,0 +1,28 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command adds existing subnets to existing shared networks."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network4-subnet-add\",",
+ " \"arguments\": {",
+ " \"name\": \"floor13\",",
+ " \"id\": 5",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp77\"/>",
+ "hook": "subnet_cmds",
+ "name": "network4-subnet-add",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 subnet 10.0.0.0/8 (id 5) is now part of shared network 'floor1'\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/network4-subnet-del.json b/src/share/api/network4-subnet-del.json
new file mode 100644
index 0000000..fd1c7e3
--- /dev/null
+++ b/src/share/api/network4-subnet-del.json
@@ -0,0 +1,28 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command removes a subnet that is part of an existing shared network and demotes it to a plain, stand-alone subnet."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network4-subnet-del\",",
+ " \"arguments\": {",
+ " \"name\": \"floor13\",",
+ " \"id\": 5",
+ " }",
+ " }"
+ ],
+ "description": "See <xref linkend=\"idp78\"/>",
+ "hook": "subnet_cmds",
+ "name": "network4-subnet-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 subnet 10.0.0.0/8 (id 5) is now removed from shared network 'floor13'\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/network6-add.json b/src/share/api/network6-add.json
new file mode 100644
index 0000000..aa74f75
--- /dev/null
+++ b/src/share/api/network6-add.json
@@ -0,0 +1,55 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command adds a new shared network."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network6-add\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [ {",
+ " \"name\": \"floor13\",",
+ " \"subnet6\": [",
+ " {",
+ " \"id\": 100,",
+ " \"pools\": [ { \"pool\": \"2003:db8:1::1-2003:db8:1::ff\" } ],",
+ " \"subnet\": \"2003:db8:1::/64\",",
+ " \"option-data\": [",
+ " {",
+ " \"name\": \"dns-servers\",",
+ " \"data\": \"2005:db8:1::1\"",
+ " }",
+ " ]",
+ " },",
+ " {",
+ " \"id\": 101,",
+ " \"pools\": [ { \"pool\": \"2003:db8:2::1-2003:db8:2::ff\" } ],",
+ " \"subnet\": \"2003:db8:2::/64\",",
+ " \"option-data\": [",
+ " {",
+ " \"name\": \"dns-servers\",",
+ " \"data\": \"2006:db8:1::1\"",
+ " }",
+ " ]",
+ " } ]",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp75\"/>",
+ "hook": "subnet_cmds",
+ "name": "network6-add",
+ "resp-syntax": [
+ "{",
+ " \"arguments\": {",
+ " \"shared-networks\": [ { \"name\": \"floor13\" } ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"A new IPv6 shared network 'floor13' added\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/network6-del.json b/src/share/api/network6-del.json
new file mode 100644
index 0000000..12eae7a
--- /dev/null
+++ b/src/share/api/network6-del.json
@@ -0,0 +1,35 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command deletes existing shared networks."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network6-del\",",
+ " \"arguments\": {",
+ " \"name\": \"floor13\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp76\"/>",
+ "hook": "subnet_cmds",
+ "name": "network6-del",
+ "resp-syntax": [
+ "{",
+ " \"command\": \"network6-del\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": \"floor13\"",
+ " }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 shared network 'floor13' deleted\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/network6-get.json b/src/share/api/network6-get.json
new file mode 100644
index 0000000..9dd727d
--- /dev/null
+++ b/src/share/api/network6-get.json
@@ -0,0 +1,57 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command retrieves detailed information about shared networks, including subnets that are currently part of a given network."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network4-get\",",
+ " \"arguments\": {",
+ " \"name\": \"floor13\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp74\"/>",
+ "hook": "subnet_cmds",
+ "name": "network6-get",
+ "resp-comment": [
+ "Note that the actual response contains many additional fields that are omitted here for clarity."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"Info about IPv6 shared network 'floor13' returned\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"match-client-id\": true,",
+ " \"name\": \"floor13\",",
+ " \"option-data\": [ ],",
+ " \"rebind-timer\": 90,",
+ " \"relay\": {",
+ " \"ip-address\": \"::\"",
+ " },",
+ " \"renew-timer\": 60,",
+ " \"subnet6\": [",
+ " {",
+ " \"subnet\": \"2003:db8:1::/64\",",
+ " \"id\": 5,",
+ " // many other subnet specific details here",
+ " },",
+ " {",
+ " \"subnet\": \"2003:db8:2::/71\",",
+ " \"id\": 6,",
+ " // many other subnet specific details here",
+ " }",
+ " ],",
+ " \"valid-lifetime\": 120",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/network6-list.json b/src/share/api/network6-list.json
new file mode 100644
index 0000000..98a64e2
--- /dev/null
+++ b/src/share/api/network6-list.json
@@ -0,0 +1,30 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command retrieves the full list of currently configured shared networks."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network6-list\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-network6-list\"/>",
+ "hook": "subnet_cmds",
+ "name": "network6-list",
+ "resp-syntax": [
+ "{",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " { \"name\": \"floor1\" },",
+ " { \"name\": \"office\" }",
+ " ]",
+ " },",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv6 network(s) found\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/network6-subnet-add.json b/src/share/api/network6-subnet-add.json
new file mode 100644
index 0000000..09d9086
--- /dev/null
+++ b/src/share/api/network6-subnet-add.json
@@ -0,0 +1,28 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command adds existing subnets to existing shared networks."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network6-subnet-add\",",
+ " \"arguments\": {",
+ " \"name\": \"floor13\",",
+ " \"id\": 5",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp77\"/>",
+ "hook": "subnet_cmds",
+ "name": "network6-subnet-add",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 subnet 2003:db8::/64 (id 5) is now part of shared network 'floor1'\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/network6-subnet-del.json b/src/share/api/network6-subnet-del.json
new file mode 100644
index 0000000..d766f89
--- /dev/null
+++ b/src/share/api/network6-subnet-del.json
@@ -0,0 +1,28 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command removes a subnet that is part of an existing shared network and demotes it to a plain, stand-alone subnet."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"network6-subnet-del\",",
+ " \"arguments\": {",
+ " \"name\": \"floor13\",",
+ " \"id\": 5",
+ " }",
+ " }"
+ ],
+ "description": "See <xref linkend=\"idp78\"/>",
+ "hook": "subnet_cmds",
+ "name": "network6-subnet-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 subnet 2003:db8::/64 (id 5) is now removed from shared network 'floor13'\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-class4-del.json b/src/share/api/remote-class4-del.json
new file mode 100644
index 0000000..af73605
--- /dev/null
+++ b/src/share/api/remote-class4-del.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.9.10",
+ "brief": [
+ "This command deletes a DHCPv4 client class from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one name of the client class to be deleted. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-class4-del\",",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " \"name\": <client class name>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-class4-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv4 client class(es) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-class4-get-all.json b/src/share/api/remote-class4-get-all.json
new file mode 100644
index 0000000..639fc36
--- /dev/null
+++ b/src/share/api/remote-class4-get-all.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.9.10",
+ "brief": [
+ "This command fetches all DHCPv4 client classes for specified servers from the configuration database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is required for this command, and must not be empty."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-class4-get-all\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-class4-get-all",
+ "resp-comment": [
+ "The returned response contains a list of maps. Each map contains a client class name and the metadata, which provides database-specific information associated with the client class."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 DHCPv4 client class(es) found.\",",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " <first client class specification>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ " },",
+ " {",
+ " <second client class specification>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, ... ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-class4-get.json b/src/share/api/remote-class4-get.json
new file mode 100644
index 0000000..5dfdcb5
--- /dev/null
+++ b/src/share/api/remote-class4-get.json
@@ -0,0 +1,51 @@
+{
+ "access": "read",
+ "avail": "1.9.10",
+ "brief": [
+ "This command fetches a selected DHCPv4 client class by name from the specified database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one name of the client class to be returned. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-class4-get\",",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " \"name\": <client class name>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-class4-get",
+ "resp-comment": [
+ "The metadata is included in the returned shared network definition and provides the database-specific information associated with the returned object."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 client class found.\",",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " \"name\": <client class name>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " },",
+ " <the rest of the client class information>",
+ " }",
+ " ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-class4-set.json b/src/share/api/remote-class4-set.json
new file mode 100644
index 0000000..23c7af0
--- /dev/null
+++ b/src/share/api/remote-class4-set.json
@@ -0,0 +1,44 @@
+{
+ "access": "write",
+ "avail": "1.9.10",
+ "brief": [
+ "This command creates or replaces a DHCPv4 client class in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one client class specification. It may contain an optional parameter \"follow-class-name\" which can specify an existing class name to indicate that the class from the command is placed right after this existing class in the hierarchy. This parameter can be omitted or set to \"null\" to indicate that the new client class should be appended at the end of the hierarchy or an updated class should remain at the current position. The ``server-tags`` list is mandatory and must contain one or more server tags as strings to explicitly associate the client class with one or more user-defined servers. It may include the special server tag \"all\" to associate the class with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-class4-set\",",
+ " \"arguments\": {",
+ " \"client-class\": [",
+ " {",
+ " <client class specification>",
+ " \"follow-class-name\": <existing class name>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-class4-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 shared network successfully set.\"",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " \"name\": <set client class name>",
+ " }",
+ " ]",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-class6-del.json b/src/share/api/remote-class6-del.json
new file mode 100644
index 0000000..f74578d
--- /dev/null
+++ b/src/share/api/remote-class6-del.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.9.10",
+ "brief": [
+ "This command deletes a DHCPv6 client class from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one name of the client class to be deleted. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-class6-del\",",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " \"name\": <client class name>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-class6-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv6 client class(es) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-class6-get-all.json b/src/share/api/remote-class6-get-all.json
new file mode 100644
index 0000000..216611c
--- /dev/null
+++ b/src/share/api/remote-class6-get-all.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.9.10",
+ "brief": [
+ "This command fetches all DHCPv6 client classes for specified servers from the configuration database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is required for this command, and must not be empty."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-class6-get-all\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-class6-get-all",
+ "resp-comment": [
+ "The returned response contains a list of maps. Each map contains a client class name and the metadata, which provides database-specific information associated with the client class."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 DHCPv6 client class(es) found.\",",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " <first client class specification>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ " },",
+ " {",
+ " <second client class specification>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, ... ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-class6-get.json b/src/share/api/remote-class6-get.json
new file mode 100644
index 0000000..bf90fbe
--- /dev/null
+++ b/src/share/api/remote-class6-get.json
@@ -0,0 +1,51 @@
+{
+ "access": "read",
+ "avail": "1.9.10",
+ "brief": [
+ "This command fetches a selected DHCPv6 client class by name from the specified database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one name of the client class to be returned. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-class6-get\",",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " \"name\": <client class name>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-class6-get",
+ "resp-comment": [
+ "The metadata is included in the returned shared network definition and provides the database-specific information associated with the returned object."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 client class found.\",",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " \"name\": <client class name>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " },",
+ " <the rest of the client class information>",
+ " }",
+ " ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-class6-set.json b/src/share/api/remote-class6-set.json
new file mode 100644
index 0000000..f23706b
--- /dev/null
+++ b/src/share/api/remote-class6-set.json
@@ -0,0 +1,44 @@
+{
+ "access": "write",
+ "avail": "1.9.10",
+ "brief": [
+ "This command creates or replaces a DHCPv6 client class in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one client class specification. It may contain an optional parameter \"follow-class-name\" which can specify an existing class name to indicate that the class from the command is placed right after this existing class in the hierarchy. This parameter can be omitted or set to \"null\" to indicate that the new client class should be appended at the end of the hierarchy or an updated class should remain at the current position. The ``server-tags`` list is mandatory and must contain one or more server tags as strings to explicitly associate the client class with one or more user-defined servers. It may include the special server tag \"all\" to associate the class with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-class6-set\",",
+ " \"arguments\": {",
+ " \"client-class\": [",
+ " {",
+ " <client class specification>",
+ " \"follow-class-name\": <existing class name>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-class6-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 shared network successfully set.\"",
+ " \"arguments\": {",
+ " \"client-classes\": [",
+ " {",
+ " \"name\": <set client class name>",
+ " }",
+ " ]",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-global-parameter4-del.json b/src/share/api/remote-global-parameter4-del.json
new file mode 100644
index 0000000..3c1564a
--- /dev/null
+++ b/src/share/api/remote-global-parameter4-del.json
@@ -0,0 +1,36 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a global DHCPv4 parameter from the configuration database. The server uses the value specified in the configuration file, or a default value if the parameter is not specified, after deleting the parameter from the database."
+ ],
+ "cmd-comment": [
+ "This command carries the list including exactly one name of the parameter to be deleted. The ``server-tags`` list is mandatory and it must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-global-parameter4-del\",",
+ " \"arguments\": {",
+ " \"parameters\": [ <parameter name as string> ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-global-parameter4-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 global parameter(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-global-parameter4-get-all.json b/src/share/api/remote-global-parameter4-get-all.json
new file mode 100644
index 0000000..1eb4297
--- /dev/null
+++ b/src/share/api/remote-global-parameter4-get-all.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches all global parameters for the server from the specified database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The special server tag \"all\" is allowed; it fetches the global parameters shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-global-parameter4-get-all\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-global-parameter4-get-all",
+ "resp-comment": [
+ "The returned response contains a list of maps. Each map contains a global parameter name/value pair. The value may be a JSON string, integer, real, or boolean. The metadata is appended to each parameter and provides database-specific information associated with the returned objects. If the server tag \"all\" is included in the command, the response contains the global parameters shared among all servers. It excludes server-specific global parameters. If an explicit server tag is included in the command, the response contains all global parameters directly associated with the given server, and the global parameters associated with all servers when server-specific values are not present."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 global parameters found.\",",
+ " \"arguments\": {",
+ " \"parameters\": [",
+ " {",
+ " <first parameter name>: <first parameter value>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " },",
+ " {",
+ " <second parameter name>: <second parameter value>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-global-parameter4-get.json b/src/share/api/remote-global-parameter4-get.json
new file mode 100644
index 0000000..1d7f09b
--- /dev/null
+++ b/src/share/api/remote-global-parameter4-get.json
@@ -0,0 +1,45 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches the selected global parameter for the server from the specified database."
+ ],
+ "cmd-comment": [
+ "This command carries a list including exactly one name of the parameter to be fetched. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it fetches the global parameter value shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-global-parameter4-get\",",
+ " \"arguments\": {",
+ " \"parameters\": [ <parameter name as string> ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-global-parameter4-get",
+ "resp-comment": [
+ "The returned response contains a map with a global parameter name/value pair. The value may be a JSON string, integer, real, or boolean. The metadata is included and provides database-specific information associated with the returned object. If the \"all\" server tag is specified, the command attempts to fetch the global parameter value associated with all servers. If the explicit server tag is specified, the command fetches the value associated with the given server. If the server-specific value does not exist, the ``remote-global-parameter4-get`` command fetches the value associated with all servers."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 global parameter found.\",",
+ " \"arguments\": {",
+ " \"parameters\": {",
+ " <parameter name>: <parameter value>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " },",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-global-parameter4-set.json b/src/share/api/remote-global-parameter4-set.json
new file mode 100644
index 0000000..d09af68
--- /dev/null
+++ b/src/share/api/remote-global-parameter4-set.json
@@ -0,0 +1,43 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or updates one or more global parameters in the configuration database."
+ ],
+ "cmd-comment": [
+ "This command carries multiple global parameters with their values. Care should be taken when specifying more than one parameter; in some cases, only a subset of the parameters may be successfully stored in the database and other parameters may fail to be stored. In such cases the ``remote-global-parameter4-get-all`` command may be useful to verify the contents of the database after the update. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it associates the specified parameters with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-global-parameter4-set\",",
+ " \"arguments\": {",
+ " \"parameters\": {",
+ " <first parameter name>: <first parameter value>,",
+ " <second parameter name>: <second parameter value>",
+ " },",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-global-parameter4-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 global parameter(s) successfully set.\",",
+ " \"arguments\": {",
+ " \"parameters\": {",
+ " <first parameter name>: <first parameter value>,",
+ " <second parameter name>: <second parameter value>",
+ " },",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-global-parameter6-del.json b/src/share/api/remote-global-parameter6-del.json
new file mode 100644
index 0000000..545921e
--- /dev/null
+++ b/src/share/api/remote-global-parameter6-del.json
@@ -0,0 +1,36 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a global DHCPv6 parameter from the configuration database. The server uses the value specified in the configuration file, or a default value if the parameter is not specified in the configuration file, after deleting the parameter from the database."
+ ],
+ "cmd-comment": [
+ "This command carries the list including exactly one name of the parameter to be deleted. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-global-parameter6-del\",",
+ " \"arguments\": {",
+ " \"parameters\": [ <parameter name as string> ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-global-parameter6-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 global parameter(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-global-parameter6-get-all.json b/src/share/api/remote-global-parameter6-get-all.json
new file mode 100644
index 0000000..12d8541
--- /dev/null
+++ b/src/share/api/remote-global-parameter6-get-all.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches all global parameters for the server from the specified database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The special server tag \"all\" is allowed; it fetches the global parameters shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-global-parameter6-get-all\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-global-parameter6-get-all",
+ "resp-comment": [
+ "The returned response contains a list of maps. Each map contains a global parameter name/value pair. The value may be a JSON string, integer, real, or boolean. The metadata is appended to each parameter and provides database-specific information associated with the returned objects. If the server tag \"all\" is included in the command, the response contains the global parameters shared among all servers. It excludes server-specific global parameters. If an explicit server tag is included in the command, the response contains all global parameters directly associated with the given server, and the global parameters associated with all servers when server-specific values are not present."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 global parameters found.\",",
+ " \"arguments\": {",
+ " \"parameters\": [",
+ " {",
+ " <first parameter name>: <first parameter value>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " },",
+ " {",
+ " <second parameter name>: <second parameter value>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-global-parameter6-get.json b/src/share/api/remote-global-parameter6-get.json
new file mode 100644
index 0000000..4c3975e
--- /dev/null
+++ b/src/share/api/remote-global-parameter6-get.json
@@ -0,0 +1,45 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches the selected global parameter for the server from the specified database."
+ ],
+ "cmd-comment": [
+ "This command carries a list including exactly one name of the parameter to be fetched. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it fetches the global parameter value shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-global-parameter6-get\",",
+ " \"arguments\": {",
+ " \"parameters\": [ <parameter name as string> ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-global-parameter6-get",
+ "resp-comment": [
+ "The returned response contains a map with a global parameter name/value pair. The value may be a JSON string, integer, real, or boolean. The metadata is included and provides database-specific information associated with the returned object. If the \"all\" server tag is specified, the command attempts to fetch the global parameter value associated with all servers. If the explicit server tag is specified, the command fetches the value associated with the given server. If the server-specific value does not exist, the ``remote-global-parameter6-get`` fetches the value associated with all servers."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 global parameter found.\",",
+ " \"arguments\": {",
+ " \"parameters\": {",
+ " <parameter name>: <parameter value>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " },",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-global-parameter6-set.json b/src/share/api/remote-global-parameter6-set.json
new file mode 100644
index 0000000..2f1fc67
--- /dev/null
+++ b/src/share/api/remote-global-parameter6-set.json
@@ -0,0 +1,43 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or updates one or more global parameters in the configuration database."
+ ],
+ "cmd-comment": [
+ "This command carries multiple global parameters with their values. Care should be taken when specifying more than one parameter; in some cases, only a subset of the parameters may be successfully stored in the database and other parameters may fail to be stored. In such cases the ``remote-global-parameter6-get-all`` command may be useful to verify the contents of the database after the update. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it associates the specified parameters with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-global-parameter6-set\",",
+ " \"arguments\": {",
+ " \"parameters\": {",
+ " <first parameter name>: <first parameter value>,",
+ " <second parameter name>: <second parameter value>",
+ " },",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-global-parameter6-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 global parameter(s) successfully set.\",",
+ " \"arguments\": {",
+ " \"parameters\": {",
+ " <first parameter name>: <first parameter value>,",
+ " <second parameter name>: <second parameter value>",
+ " },",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-network4-del.json b/src/share/api/remote-network4-del.json
new file mode 100644
index 0000000..2c3b5c8
--- /dev/null
+++ b/src/share/api/remote-network4-del.json
@@ -0,0 +1,40 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes an IPv4 shared network from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one name of the shared network to be deleted. The ``subnets-action`` parameter denotes whether the subnets in this shared network should be deleted. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-network4-del\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <shared network name>",
+ " }",
+ " ],",
+ " \"subnets-action\": <'keep' | 'delete'>,",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-network4-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv4 shared network(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-network4-get.json b/src/share/api/remote-network4-get.json
new file mode 100644
index 0000000..5ad6b48
--- /dev/null
+++ b/src/share/api/remote-network4-get.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches the selected IPv4 shared network for the server from the specified database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one name of the shared network to be returned. The ``subnets-include`` optional parameter allows for specifying whether the subnets belonging to the shared network should also be returned. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-network4-get\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <shared network name>",
+ " }",
+ " ],",
+ " \"subnets-include\": <'full' | 'no'>,",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-network4-get",
+ "resp-comment": [
+ "If the subnets are returned with the shared network, they are carried in the ``subnet4`` list within the shared network definition. The metadata is included in the returned shared network definition and provides the database-specific information associated with the returned object."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 shared network found.\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <shared network name>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " },",
+ " <the rest of the shared network information, potentially including subnets>",
+ " }",
+ " ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-network4-list.json b/src/share/api/remote-network4-list.json
new file mode 100644
index 0000000..db10c68
--- /dev/null
+++ b/src/share/api/remote-network4-list.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches a list of all IPv4 shared networks from the configuration database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is required for this command, and must not be empty. It may either contain one or multiple server tags as strings, or a single ``null`` value."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-network4-list\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-network4-list",
+ "resp-comment": [
+ "The returned response contains the list of maps. Each map contains the shared network name and the metadata, which provides database-specific information associated with the shared network. The returned list does not contain full definitions of the shared networks; use ``remote-network4-get`` to fetch the full information about the selected shared networks. If the command includes explicit server tags as strings (including the special server tag \"all\"), the list contains all shared networks which are associated with any of the specified tags. A network is returned even if it is associated with multiple servers and only one of the specified tags matches. If the command includes the ``null`` value in the ``server-tags`` list, the response contains all shared networks which are assigned to no servers (unassigned)."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv4 shared network(s) found.\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <first shared network name>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ " },",
+ " {",
+ " \"name\": <second shared network name>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, ... ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-network4-set.json b/src/share/api/remote-network4-set.json
new file mode 100644
index 0000000..5f2969d
--- /dev/null
+++ b/src/share/api/remote-network4-set.json
@@ -0,0 +1,37 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces an IPv4 shared network in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one shared network specification, and must not contain subnets (the \"subnet4\" parameter). The subnets are added to the shared network using the ``remote-subnet4-set`` command. The ``server-tags`` list is mandatory and must contain one or more server tags as strings to explicitly associate the shared network with one or more user-defined servers. It may include the special server tag \"all\" to associate the network with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-network4-set\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " <shared network specification excluding subnets list>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-network4-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 shared network successfully set.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-network6-del.json b/src/share/api/remote-network6-del.json
new file mode 100644
index 0000000..2d0cf17
--- /dev/null
+++ b/src/share/api/remote-network6-del.json
@@ -0,0 +1,40 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes an IPv6 shared network from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one name of the shared network to be deleted. The ``subnets-action`` parameter indicates whether the subnets in this shared network should be deleted. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-network6-del\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <shared network name>",
+ " }",
+ " ],",
+ " \"subnets-action\": <'keep' | 'delete'>,",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-network6-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv6 shared network(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-network6-get.json b/src/share/api/remote-network6-get.json
new file mode 100644
index 0000000..2ba0274
--- /dev/null
+++ b/src/share/api/remote-network6-get.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches the selected IPv6 shared network for the server from the specified database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one name of the shared network to be returned. The ``subnets-include`` optional parameter allows for specifying whether the subnets belonging to the shared network should also be returned. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-network6-get\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <shared network name>",
+ " }",
+ " ],",
+ " \"subnets-include\": <'full' | 'no'>,",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-network6-get",
+ "resp-comment": [
+ "If the subnets are returned with the shared network, they are carried in the ``subnet6`` list within the shared network definition. The metadata is included in the returned shared network definition and provides the database-specific information associated with the returned object."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 shared network found.\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <shared network name>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " },",
+ " <the rest of the shared network information, potentially including subnets>",
+ " }",
+ " ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-network6-list.json b/src/share/api/remote-network6-list.json
new file mode 100644
index 0000000..107ba80
--- /dev/null
+++ b/src/share/api/remote-network6-list.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches a list of all IPv6 shared networks from the configuration database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is required for this command, and must not be empty. It may either contain one or multiple server tags as strings, or a single ``null`` value."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-network6-list\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-network6-list",
+ "resp-comment": [
+ "The returned response contains the list of maps. Each map contains the shared network name and the metadata, which provides database-specific information associated with the shared network. The returned list does not contain full definitions of the shared networks; use ``remote-network6-get`` to fetch the full information about the selected shared networks. If the command includes explicit server tags as strings (including the special server tag \"all\"), the list contains all shared networks which are associated with any of the specified tags. A network is returned even if it is associated with multiple servers and only one of the specified tags matches. If the command includes the ``null`` value in the ``server-tags`` list, the response contains all shared networks which are assigned to no servers (unassigned)."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv6 shared network(s) found.\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <first shared network name>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ " },",
+ " {",
+ " \"name\": <second shared network name>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, ... ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-network6-set.json b/src/share/api/remote-network6-set.json
new file mode 100644
index 0000000..a540124
--- /dev/null
+++ b/src/share/api/remote-network6-set.json
@@ -0,0 +1,37 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces an IPv6 shared network in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one shared network specification, and must not contain subnets (the \"subnet6\" parameter). The subnets are added to the shared network using the ``remote-subnet6-set`` command. The ``server-tags`` list is mandatory and must contain one or more server tags as strings to explicitly associate the shared network with one or more user-defined servers. It may include the special server tag \"all\" to associate the network with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-network6-set\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " <shared network specification excluding subnets list>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-network6-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 shared network successfully set.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option-def4-del.json b/src/share/api/remote-option-def4-del.json
new file mode 100644
index 0000000..9c8efca
--- /dev/null
+++ b/src/share/api/remote-option-def4-del.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv4 option definition from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one option definition specification, comprising an option name and code. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option-def4-del\",",
+ " \"arguments\": {",
+ " \"option-defs\": [ {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " } ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option-def4-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv4 option definition(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option-def4-get-all.json b/src/share/api/remote-option-def4-get-all.json
new file mode 100644
index 0000000..df6752d
--- /dev/null
+++ b/src/share/api/remote-option-def4-get-all.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches all DHCPv4 option definitions from the configuration database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The special server tag \"all\" is allowed, to fetch the option definitions shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option-def4-get-all\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option-def4-get-all",
+ "resp-comment": [
+ "The returned response contains a list of maps. Each map contains an option definition specification and the metadata, including database-specific information associated with the returned objects. If the server tag \"all\" is included in the command, the response contains the option definitions shared among all servers. It excludes server-specific option definitions. If an explicit server tag is included in the command, the response contains all option definitions directly associated with the given server, and the option definitions associated with all servers when server-specific option definitions are not present."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 DHCPv4 option definition(s) found.\",",
+ " \"arguments\": {",
+ " \"option-defs\": [",
+ " {",
+ " <first option definition>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " },",
+ " {",
+ " <second option definition>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option-def4-get.json b/src/share/api/remote-option-def4-get.json
new file mode 100644
index 0000000..c48fda4
--- /dev/null
+++ b/src/share/api/remote-option-def4-get.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches a DHCPv4 option definition from the configuration database."
+ ],
+ "cmd-comment": [
+ "The desired option definition is identified by the pair of option code/space values. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed, to fetch the option definition instance shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option-def4-get\",",
+ " \"arguments\": {",
+ " \"option-defs\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option-def4-get",
+ "resp-comment": [
+ "The metadata is included and provides database-specific information associated with the returned object. If the \"all\" server tag is specified, the command attempts to fetch the option definition associated with all servers. If the explicit server tag is specified, the command fetches the option definition associated with the given server. If the server-specific option definition does not exist, the ``remote-option-def4-get`` command fetches the option definition associated with all servers."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 option definition found.\",",
+ " \"arguments\": {",
+ " \"option-defs\": [",
+ " {",
+ " <option definition>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option-def4-set.json b/src/share/api/remote-option-def4-set.json
new file mode 100644
index 0000000..78a27e8
--- /dev/null
+++ b/src/share/api/remote-option-def4-set.json
@@ -0,0 +1,37 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv4 option definition in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one option definition specification. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it associates the specified option definition with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option-def4-set\",",
+ " \"arguments\": {",
+ " \"option-defs\": [",
+ " {",
+ " <option definition specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option-def4-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 option definition set.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option-def6-del.json b/src/share/api/remote-option-def6-del.json
new file mode 100644
index 0000000..9121c6e
--- /dev/null
+++ b/src/share/api/remote-option-def6-del.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv6 option definition from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one option definition specification, comprising an option name and code. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option-def6-del\",",
+ " \"arguments\": {",
+ " \"option-defs\": [ {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " } ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option-def6-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv6 option definition(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option-def6-get-all.json b/src/share/api/remote-option-def6-get-all.json
new file mode 100644
index 0000000..9d64358
--- /dev/null
+++ b/src/share/api/remote-option-def6-get-all.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches all DHCPv6 option definitions from the configuration database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The special server tag \"all\" is allowed, to fetch the option definitions shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option-def6-get-all\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option-def6-get-all",
+ "resp-comment": [
+ "The returned response contains a list of maps. Each map contains an option definition specification and the metadata, including database-specific information associated with the returned objects. If the server tag \"all\" is included in the command, the response contains the option definitions shared among all servers. It excludes server-specific option definitions. If an explicit server tag is included in the command, the response contains all option definitions directly associated with the given server, and the option definitions associated with all servers when server-specific option definitions are not present."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 DHCPv6 option definition(s) found.\",",
+ " \"arguments\": {",
+ " \"option-defs\": [",
+ " {",
+ " <first option definition>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " },",
+ " {",
+ " <second option definition>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option-def6-get.json b/src/share/api/remote-option-def6-get.json
new file mode 100644
index 0000000..f7c5895
--- /dev/null
+++ b/src/share/api/remote-option-def6-get.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches a DHCPv6 option definition from the configuration database."
+ ],
+ "cmd-comment": [
+ "The desired option definition is identified by the pair of option code/space values. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed, to fetch the option definition instance shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option-def6-get\",",
+ " \"arguments\": {",
+ " \"option-defs\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option-def6-get",
+ "resp-comment": [
+ "The metadata is included and provides database-specific information associated with the returned object. If the \"all\" server tag is specified, the command fetches the option definition associated with all servers. If the explicit server tag is specified, the command fetches the option definition associated with the given server. If the server-specific option definition does not exist, the ``remote-option-def6-get`` command fetches the option definition associated with all servers."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 option definition found.\",",
+ " \"arguments\": {",
+ " \"option-defs\": [",
+ " {",
+ " <option definition>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option-def6-set.json b/src/share/api/remote-option-def6-set.json
new file mode 100644
index 0000000..1a32e82
--- /dev/null
+++ b/src/share/api/remote-option-def6-set.json
@@ -0,0 +1,37 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv6 option definition in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one option definition specification. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it associates the specified option definition with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option-def6-set\",",
+ " \"arguments\": {",
+ " \"option-defs\": [",
+ " {",
+ " <option definition specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option-def6-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 option definition set.\"",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option4-global-del.json b/src/share/api/remote-option4-global-del.json
new file mode 100644
index 0000000..15b88db
--- /dev/null
+++ b/src/share/api/remote-option4-global-del.json
@@ -0,0 +1,41 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv4 global option from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one option specification, comprising an option name and code. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option4-global-del\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option4-global-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv4 option(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option4-global-get-all.json b/src/share/api/remote-option4-global-get-all.json
new file mode 100644
index 0000000..d64ff31
--- /dev/null
+++ b/src/share/api/remote-option4-global-get-all.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches all DHCPv4 global options for the server from the configuration database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The special server tag \"all\" is allowed, to fetch the global options shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option4-global-get-all\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option4-global-get-all",
+ "resp-comment": [
+ "The returned response contains a list of maps. Each map contains a global option specification and the metadata, including database-specific information associated with the returned object. If the server tag \"all\" is included in the command, the response contains the global options shared among all servers. It excludes server-specific global options. If an explicit server tag is included in the command, the response contains all global options directly associated with the given server, and the options associated with all servers when server-specific options are not present."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 DHCPv4 option(s) found.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " <first option specification>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " },",
+ " {",
+ " <second option specification>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option4-global-get.json b/src/share/api/remote-option4-global-get.json
new file mode 100644
index 0000000..f6a11bf
--- /dev/null
+++ b/src/share/api/remote-option4-global-get.json
@@ -0,0 +1,51 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches a global DHCPv4 option for the server from the specified database."
+ ],
+ "cmd-comment": [
+ "The option is identified by the pair of option code/space values. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed, to fetch the global option instance shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option4-global-get\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option4-global-get",
+ "resp-comment": [
+ "The metadata is included and provides database specific information associated with the returned object. If the \"all\" server tag is specified, the command fetches the global option associated with all servers. If the explicit server tag is specified, the command fetches the global option associated with the given server. If the server specific option does not exist, it fetches the option associated with all servers."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 option is found.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " <option information>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option4-global-set.json b/src/share/api/remote-option4-global-set.json
new file mode 100644
index 0000000..32c1611
--- /dev/null
+++ b/src/share/api/remote-option4-global-set.json
@@ -0,0 +1,45 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv4 global option in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one option specification. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it associates the specified option with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option4-global-set\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " <global option specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option4-global-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 option set.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option4-network-del.json b/src/share/api/remote-option4-network-del.json
new file mode 100644
index 0000000..6ba9f99
--- /dev/null
+++ b/src/share/api/remote-option4-network-del.json
@@ -0,0 +1,45 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv4 option from a shared network from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes two lists with exactly one name of the shared network and exactly one option specification, comprising an option name and code. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option4-network-del\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <shared network name>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option4-network-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv4 option(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option4-network-set.json b/src/share/api/remote-option4-network-set.json
new file mode 100644
index 0000000..ad9cc06
--- /dev/null
+++ b/src/share/api/remote-option4-network-set.json
@@ -0,0 +1,49 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv4 option in a shared network in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided lists must contain exactly one name of the shared network and one option specification. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option4-network-set\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <shared network name>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " <shared network option specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option4-network-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 option successfully set.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option4-pool-del.json b/src/share/api/remote-option4-pool-del.json
new file mode 100644
index 0000000..b54ec2b
--- /dev/null
+++ b/src/share/api/remote-option4-pool-del.json
@@ -0,0 +1,45 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv4 option from an address pool from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes two lists with exactly one address pool specification and exactly one option specification comprising an option space name and code. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option4-pool-del\",",
+ " \"arguments\": {",
+ " \"pools\": [",
+ " {",
+ " \"pool\": <pool range or prefix>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option4-pool-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv4 option(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option4-pool-set.json b/src/share/api/remote-option4-pool-set.json
new file mode 100644
index 0000000..6567141
--- /dev/null
+++ b/src/share/api/remote-option4-pool-set.json
@@ -0,0 +1,49 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv4 option in an address pool in the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes two lists with exactly address pool specification and exactly one option specification. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option4-pool-set\",",
+ " \"arguments\": {",
+ " \"pools\": [",
+ " {",
+ " \"pool\": <pool range or prefix>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " <address pool option specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option4-pool-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 option successfully set.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option4-subnet-del.json b/src/share/api/remote-option4-subnet-del.json
new file mode 100644
index 0000000..c43fff8
--- /dev/null
+++ b/src/share/api/remote-option4-subnet-del.json
@@ -0,0 +1,45 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv4 option from a subnet from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes two lists with exactly one ID of the subnet and exactly one option specification, comprising an option name and code. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option4-subnet-del\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option4-subnet-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv4 option(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option4-subnet-set.json b/src/share/api/remote-option4-subnet-set.json
new file mode 100644
index 0000000..e332f72
--- /dev/null
+++ b/src/share/api/remote-option4-subnet-set.json
@@ -0,0 +1,49 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv4 option in a subnet in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided lists must contain exactly one ID of the subnet and one option specification. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option4-subnet-set\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " <subnet option specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option4-subnet-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 option successfully set.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-option6-global-del.json b/src/share/api/remote-option6-global-del.json
new file mode 100644
index 0000000..e5c3a35
--- /dev/null
+++ b/src/share/api/remote-option6-global-del.json
@@ -0,0 +1,41 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv6 global option from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one option specification, comprising an option name and code. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-global-del\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-global-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv6 option(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-global-get-all.json b/src/share/api/remote-option6-global-get-all.json
new file mode 100644
index 0000000..616354d
--- /dev/null
+++ b/src/share/api/remote-option6-global-get-all.json
@@ -0,0 +1,52 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches all DHCPv6 global options for the server from the configuration database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The special server tag \"all\" is allowed, to fetch the global options shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-global-get-all\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-global-get-all",
+ "resp-comment": [
+ "The returned response contains a list of maps. Each map contains a global option specification and the metadata, including database-specific information associated with the returned object. If the server tag \"all\" is included in the command, the response contains the global options shared between all servers. It excludes server-specific global options. If an explicit server tag is included in the command, the response contains all global options directly associated with the given server, and the options associated with all servers when server-specific options are not present."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 DHCPv6 option(s) found.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " <first option specification>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " },",
+ " {",
+ " <second option specification>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-global-get.json b/src/share/api/remote-option6-global-get.json
new file mode 100644
index 0000000..487c3de
--- /dev/null
+++ b/src/share/api/remote-option6-global-get.json
@@ -0,0 +1,51 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches a global DHCPv6 option for the server from the specified database."
+ ],
+ "cmd-comment": [
+ "The option is identified by the pair of option code/space values. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed, to fetch the global option instance shared by all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-global-get\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-global-get",
+ "resp-comment": [
+ "The metadata is included and provides database-specific information associated with the returned object. If the \"all\" server tag is specified, the command attempts to fetch the global option associated with all servers. If the explicit server tag is specified, the command will fetch the global option associated with the given server. If the server-specific option does not exist, it fetches the option associated with all servers."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 option is found.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " <option information>,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <server tag> ]",
+ " }",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-global-set.json b/src/share/api/remote-option6-global-set.json
new file mode 100644
index 0000000..2727403
--- /dev/null
+++ b/src/share/api/remote-option6-global-set.json
@@ -0,0 +1,45 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv6 global option in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one option specification. The ``server-tags`` list is mandatory and must contain exactly one server tag. Specifying an empty list, a value of ``null``, or multiple server tags will result in an error. The server tag \"all\" is allowed; it associates the specified option with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-global-set\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " <global option specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <single server tag as string> ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-global-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 option set.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-network-del.json b/src/share/api/remote-option6-network-del.json
new file mode 100644
index 0000000..6a81929
--- /dev/null
+++ b/src/share/api/remote-option6-network-del.json
@@ -0,0 +1,45 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv6 option from a shared network from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes two lists with exactly one name of the shared network and exactly one option specification, comprising an option name and code. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-network-del\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <shared network name>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-network-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv6 option(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-network-set.json b/src/share/api/remote-option6-network-set.json
new file mode 100644
index 0000000..ab989b3
--- /dev/null
+++ b/src/share/api/remote-option6-network-set.json
@@ -0,0 +1,49 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv6 option in a shared network in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided lists must contain exactly one name of the shared network and one option specification. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-network-set\",",
+ " \"arguments\": {",
+ " \"shared-networks\": [",
+ " {",
+ " \"name\": <shared network name>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " <shared network option specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-network-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 option successfully set.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-pd-pool-del.json b/src/share/api/remote-option6-pd-pool-del.json
new file mode 100644
index 0000000..8fba741
--- /dev/null
+++ b/src/share/api/remote-option6-pd-pool-del.json
@@ -0,0 +1,46 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv6 option from a prefix delegation pool from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes two lists with exactly one prefix delegation pool specification and exactly one option specification, comprising an option name and code. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-pd-pool-del\",",
+ " \"arguments\": {",
+ " \"pd-pools\": [",
+ " {",
+ " \"prefix\": <pool prefix (address part)>",
+ " \"prefix-len\": <pool prefix (length part)>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-pd-pool-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv6 option(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-pd-pool-set.json b/src/share/api/remote-option6-pd-pool-set.json
new file mode 100644
index 0000000..753bd51
--- /dev/null
+++ b/src/share/api/remote-option6-pd-pool-set.json
@@ -0,0 +1,50 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv6 option in a prefix delegation pool in the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes two lists with exactly one prefix delegation pool specification and exactly one option specification. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-pd-pool-set\",",
+ " \"arguments\": {",
+ " \"pd-pools\": [",
+ " {",
+ " \"prefix\": <pool prefix (address part)>",
+ " \"prefix-len\": <pool prefix (length part)>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " <prefix delegation pool option specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-pd-pool-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 option successfully set.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-pool-del.json b/src/share/api/remote-option6-pool-del.json
new file mode 100644
index 0000000..1fafd83
--- /dev/null
+++ b/src/share/api/remote-option6-pool-del.json
@@ -0,0 +1,45 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv6 option from an address pool from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes two lists with exactly one address pool specification and exactly one option specification, comprising an option name and code. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-pool-del\",",
+ " \"arguments\": {",
+ " \"pools\": [",
+ " {",
+ " \"pool\": <pool range or prefix>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-pool-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv6 option(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-pool-set.json b/src/share/api/remote-option6-pool-set.json
new file mode 100644
index 0000000..ae45e3e
--- /dev/null
+++ b/src/share/api/remote-option6-pool-set.json
@@ -0,0 +1,49 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv6 option in an address pool in the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes two lists with exactly address pool specification and exactly one option specification. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-pool-set\",",
+ " \"arguments\": {",
+ " \"pools\": [",
+ " {",
+ " \"pool\": <pool range or prefix>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " <address pool option specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-pool-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 option successfully set.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-subnet-del.json b/src/share/api/remote-option6-subnet-del.json
new file mode 100644
index 0000000..ff24ed3
--- /dev/null
+++ b/src/share/api/remote-option6-subnet-del.json
@@ -0,0 +1,45 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes a DHCPv6 option from a subnet from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes two lists with exactly one ID of the subnet and exactly one option specification, comprising an option name and code. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-subnet-del\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-subnet-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv6 option(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-option6-subnet-set.json b/src/share/api/remote-option6-subnet-set.json
new file mode 100644
index 0000000..d0097d6
--- /dev/null
+++ b/src/share/api/remote-option6-subnet-set.json
@@ -0,0 +1,49 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces a DHCPv6 option in a subnet in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided lists must contain exactly one ID of the subnet and one option specification. Specifying an empty list, a value of ``null``, or a server tag will result in an error."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-option6-subnet-set\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>",
+ " }",
+ " ],",
+ " \"options\": [",
+ " {",
+ " <subnet option specification>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-option6-subnet-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 option successfully set.\",",
+ " \"arguments\": {",
+ " \"options\": [",
+ " {",
+ " \"code\": <option code>,",
+ " \"space\": <option space>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-server4-del.json b/src/share/api/remote-server4-del.json
new file mode 100644
index 0000000..4f7718c
--- /dev/null
+++ b/src/share/api/remote-server4-del.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes information about a DHCPv4 server from the configuration database. Any configuration explicitly associated with the deleted server is automatically disassociated. In addition, configuration elements not shareable with other servers (e.g. global DHCP parameters) are deleted. Shareable configuration elements (e.g. subnets, shared networks) are not deleted as they may be used by other servers."
+ ],
+ "cmd-comment": [
+ "This command carries the list including exactly one map with the tag of the server to be deleted."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-server4-del\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <server name>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-server4-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv4 server(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-server4-get-all.json b/src/share/api/remote-server4-get-all.json
new file mode 100644
index 0000000..5555752
--- /dev/null
+++ b/src/share/api/remote-server4-get-all.json
@@ -0,0 +1,47 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches information about all DHCPv4 servers specified by the user."
+ ],
+ "cmd-comment": [
+ "This command contains no arguments besides the optional ``remote``."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-server4-get-all\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-server4-get-all",
+ "resp-comment": [
+ "The returned response contain a list of maps. Each map contains a server tag uniquely identifying a server, and the user-defined description of the server. The Kea Configuration Backend uses the keyword ``all`` to associate parts of the configuration with all servers. Internally, it creates the logical server ``all`` for this purpose. However, this logical server is not returned as a result of the ``remote-server4-get-all`` command; only the user-defined servers are returned."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 servers found.\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <first server tag>,",
+ " \"description\": <first server description>",
+ " },",
+ " {",
+ " \"server-tag\": <second server tag>,",
+ " \"description\": <second server description>",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-server4-get.json b/src/share/api/remote-server4-get.json
new file mode 100644
index 0000000..c648606
--- /dev/null
+++ b/src/share/api/remote-server4-get.json
@@ -0,0 +1,48 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches information about the DHCPv4 server, such as the server tag and description."
+ ],
+ "cmd-comment": [
+ "This command carries the list including exactly one map with the tag of the server to be fetched."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-server4-get\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <server tag>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-server4-get",
+ "resp-comment": [
+ "The server tag is the unique identifier of the server, used to associate the configuration elements in the database with the particular server instance. The returned server description is specified by the user when setting the server information."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCP server 'server tag' found.\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <server tag>,",
+ " \"description\": <server description>",
+ " }",
+ " ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-server4-set.json b/src/share/api/remote-server4-set.json
new file mode 100644
index 0000000..b63e389
--- /dev/null
+++ b/src/share/api/remote-server4-set.json
@@ -0,0 +1,45 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces information about the DHCPv4 server in the database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one server specification. The ``server-tag`` must be unique across all servers within the configuration database. The ``description`` is the arbitrary text describing the server, its location within the network, etc."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-server4-set\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <server tag>,",
+ " \"description\": <server description>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-server4-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv4 server successfully set.\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <server tag>,",
+ " \"description\": <server description>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-server6-del.json b/src/share/api/remote-server6-del.json
new file mode 100644
index 0000000..5ca04c2
--- /dev/null
+++ b/src/share/api/remote-server6-del.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes information about a DHCPv6 server from the configuration database. Any configuration explicitly associated with the deleted server is automatically disassociated. In addition, configuration elements not shareable with other servers (e.g. global DHCP parameters) are deleted. Shareable configuration elements (e.g. subnets, shared networks) are not deleted as they may be used by other servers."
+ ],
+ "cmd-comment": [
+ "This command carries the list including exactly one map with the tag of the server to be deleted."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-server6-del\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <server name>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-server6-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 DHCPv6 server(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-server6-get-all.json b/src/share/api/remote-server6-get-all.json
new file mode 100644
index 0000000..ab12139
--- /dev/null
+++ b/src/share/api/remote-server6-get-all.json
@@ -0,0 +1,47 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches information about all DHCPv6 servers specified by the user."
+ ],
+ "cmd-comment": [
+ "This command contains no arguments besides the optional ``remote``."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-server6-get-all\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-server6-get-all",
+ "resp-comment": [
+ "The returned response contain a list of maps. Each map contains a server tag uniquely identifying a server, and the user-defined description of the server. The Kea Configuration Backend uses the keyword ``all`` to associate parts of the configuration with all servers. Internally, it creates the logical server ``all`` for this purpose. However, this logical server is not returned as a result of the ``remote-server6-get-all`` command; only the user-defined servers are returned."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 servers found.\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <first server tag>,",
+ " \"description\": <first server description>",
+ " },",
+ " {",
+ " \"server-tag\": <second server tag>,",
+ " \"description\": <second server description>",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-server6-get.json b/src/share/api/remote-server6-get.json
new file mode 100644
index 0000000..1acbfd4
--- /dev/null
+++ b/src/share/api/remote-server6-get.json
@@ -0,0 +1,48 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches information about the DHCPv6 server, such as the server tag and description."
+ ],
+ "cmd-comment": [
+ "This command carries the list including exactly one map with the tag of the server to be fetched."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-server6-get\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <server tag>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-server6-get",
+ "resp-comment": [
+ "The server tag is the unique identifier of the server, used to associate the configuration elements in the database with the particular server instance. The returned server description is specified by the user when setting the server information."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCP server 'server tag' found.\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <server tag>,",
+ " \"description\": <server description>",
+ " }",
+ " ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-server6-set.json b/src/share/api/remote-server6-set.json
new file mode 100644
index 0000000..211a4dc
--- /dev/null
+++ b/src/share/api/remote-server6-set.json
@@ -0,0 +1,45 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces information about the DHCPv6 server in the database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one server specification. The ``server-tag`` must be unique across all servers within the configuration database. The ``description`` is the arbitrary text describing the server, its location within the network, etc."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-server6-set\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <server tag>,",
+ " \"description\": <server description>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-server6-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"DHCPv6 server successfully set.\",",
+ " \"arguments\": {",
+ " \"servers\": [",
+ " {",
+ " \"server-tag\": <server tag>,",
+ " \"description\": <server description>",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-subnet4-del-by-id.json b/src/share/api/remote-subnet4-del-by-id.json
new file mode 100644
index 0000000..4ccf7f8
--- /dev/null
+++ b/src/share/api/remote-subnet4-del-by-id.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes an IPv4 subnet by ID from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one ID of the subnet to be deleted. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet4-del-by-id\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet4-del-by-id",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv4 subnet(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-subnet4-del-by-prefix.json b/src/share/api/remote-subnet4-del-by-prefix.json
new file mode 100644
index 0000000..7d7b1a3
--- /dev/null
+++ b/src/share/api/remote-subnet4-del-by-prefix.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes an IPv4 subnet by prefix from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one prefix of the subnet to be deleted. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet4-del-by-prefix\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"subnet\": <subnet prefix>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet4-del-by-prefix",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv4 subnet(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-subnet4-get-by-id.json b/src/share/api/remote-subnet4-get-by-id.json
new file mode 100644
index 0000000..97190a4
--- /dev/null
+++ b/src/share/api/remote-subnet4-get-by-id.json
@@ -0,0 +1,49 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches the selected IPv4 subnet by ID from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one ID of the subnet to be returned. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet4-get-by-id\",",
+ " \"arguments\": {",
+ " \"subnets\": [ {",
+ " \"id\": <subnet identifier>",
+ " } ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet4-get-by-id",
+ "resp-comment": [
+ "If the shared network name is null, it means that the returned subnet does not belong to any shared network (a global subnet). The metadata is included in the returned subnet definition and provides database-specific information associated with the returned object."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 subnet found.\",",
+ " \"arguments\": {",
+ " \"subnets\": [ {",
+ " \"id\": <subnet identifier>,",
+ " \"subnet\": <subnet prefix>,",
+ " \"shared-network-name\": <shared network name> | null,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " },",
+ " <the rest of the subnet specification here>",
+ " } ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-subnet4-get-by-prefix.json b/src/share/api/remote-subnet4-get-by-prefix.json
new file mode 100644
index 0000000..fec9e54
--- /dev/null
+++ b/src/share/api/remote-subnet4-get-by-prefix.json
@@ -0,0 +1,51 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches the selected IPv4 subnet by prefix from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one prefix of the subnet to be returned. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet4-get-by-prefix\",",
+ " \"arguments\": {",
+ " \"subnets\": [ {",
+ " \"subnet\": <subnet prefix>",
+ " } ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet4-get-by-prefix",
+ "resp-comment": [
+ "If the shared network name is null, it means that the returned subnet does not belong to any shared network (global subnet). The metadata is included in the returned subnet definition and provides database-specific information associated with the returned object."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 subnet found.\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>,",
+ " \"subnet\": <subnet prefix>,",
+ " \"shared-network-name\": <shared network name> | null,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " },",
+ " <the rest of the subnet specification here>",
+ " }",
+ " ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-subnet4-list.json b/src/share/api/remote-subnet4-list.json
new file mode 100644
index 0000000..a0c15ff
--- /dev/null
+++ b/src/share/api/remote-subnet4-list.json
@@ -0,0 +1,56 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches a list of all IPv4 subnets from the configuration database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is required for this command, and must not be empty. It may either contain one or multiple server tags as strings, or a single ``null`` value."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet4-list\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet4-list",
+ "resp-comment": [
+ "The returned response contains a list of maps. Each map contains a subnet identifier, prefix, and shared network name to which the subnet belongs. If the subnet does not belong to a shared network, the name is null. The metadata includes database-specific information associated with the subnets. The returned list does not contain full subnet definitions; use ``remote-subnet4-get`` to fetch the full information about the selected subnets. If the command includes explicit server tags as strings (including the special server tag \"all\"), the list contains all subnets which are associated with any of the specified tags. A subnet is returned even if it is associated with multiple servers and only one of the specified tags matches. If the command includes the ``null`` value in the ``server-tags`` list, the response contains all subnets which are assigned to no servers (unassigned)."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv4 subnets found.\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <first subnet identifier>,",
+ " \"subnet\": <first subnet prefix>,",
+ " \"shared-network-name\": <shared network name> | null,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ " },",
+ " {",
+ " \"id\": <second subnet identifier>,",
+ " \"subnet\": <second subnet prefix>,",
+ " \"shared-network-name\": <shared network name> | null,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, ... ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-subnet4-set.json b/src/share/api/remote-subnet4-set.json
new file mode 100644
index 0000000..45b3ece
--- /dev/null
+++ b/src/share/api/remote-subnet4-set.json
@@ -0,0 +1,44 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces an IPv4 subnet in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one subnet specification. The ``shared-network-name`` parameter is required for these commands; it associates the subnet with the shared network by its name. If the subnet must not belong to any shared network (a global subnet), the ``null`` value must be specified for the shared network name. The ``server-tags`` list is mandatory and must contain one or more server tags as strings to explicitly associate the subnet with one or more user-defined servers. The ``remote-subnet4-set`` command may include the special server tag \"all\" to associate the subnet with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet4-set\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>,",
+ " \"subnet\": <subnet prefix>,",
+ " \"shared-network-name\": <shared network name> | null,",
+ " <the rest of the subnet specification here>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet4-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 subnet successfully set.\",",
+ " \"arguments\": {",
+ " \"id\": <subnet identifier>,",
+ " \"subnet\": <subnet prefix>",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/remote-subnet6-del-by-id.json b/src/share/api/remote-subnet6-del-by-id.json
new file mode 100644
index 0000000..291cdfd
--- /dev/null
+++ b/src/share/api/remote-subnet6-del-by-id.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes an IPv6 subnet by ID from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one ID of the subnet to be deleted. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet6-del-by-id\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet6-del-by-id",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv6 subnet(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-subnet6-del-by-prefix.json b/src/share/api/remote-subnet6-del-by-prefix.json
new file mode 100644
index 0000000..5dff846
--- /dev/null
+++ b/src/share/api/remote-subnet6-del-by-prefix.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command deletes an IPv6 subnet by prefix from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one prefix of the subnet to be deleted. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet6-del-by-prefix\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"subnet\": <subnet prefix>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet6-del-by-prefix",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"1 IPv6 subnet(s) deleted.\",",
+ " \"arguments\": {",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-subnet6-get-by-id.json b/src/share/api/remote-subnet6-get-by-id.json
new file mode 100644
index 0000000..ac50327
--- /dev/null
+++ b/src/share/api/remote-subnet6-get-by-id.json
@@ -0,0 +1,53 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches the selected IPv6 subnet by ID from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one ID of the subnet to be returned. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet6-get-by-id\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet6-get-by-id",
+ "resp-comment": [
+ "If the shared network name is null, it means that the returned subnet does not belong to any shared network (a global subnet). The metadata is included in the returned subnet definition and provides database-specific information associated with the returned object."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 subnet found.\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>,",
+ " \"subnet\": <subnet prefix>,",
+ " \"shared-network-name\": <shared network name> | null,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " },",
+ " <the rest of the subnet specification here>",
+ " }",
+ " ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-subnet6-get-by-prefix.json b/src/share/api/remote-subnet6-get-by-prefix.json
new file mode 100644
index 0000000..11edb5a
--- /dev/null
+++ b/src/share/api/remote-subnet6-get-by-prefix.json
@@ -0,0 +1,51 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches the selected IPv6 subnet by prefix from the configuration database."
+ ],
+ "cmd-comment": [
+ "This command includes a list with exactly one prefix of the subnet to be returned. The ``server-tags`` parameter must not be specified for this command."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet6-get-by-prefix\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"subnet\": <subnet prefix>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " }",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet6-get-by-prefix",
+ "resp-comment": [
+ "If the shared network name is null, it means that the returned subnet does not belong to any shared network (global subnet). The metadata is included in the returned subnet definition and provides database-specific information associated with the returned object."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 subnet found.\",",
+ " \"arguments\": {",
+ " \"subnets\": [ {",
+ " \"id\": <subnet identifier>,",
+ " \"subnet\": <subnet prefix>,",
+ " \"shared-network-name\": <shared network name> | null,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " },",
+ " <the rest of the subnet specification here>",
+ " } ],",
+ " \"count\": 1",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-subnet6-list.json b/src/share/api/remote-subnet6-list.json
new file mode 100644
index 0000000..0232852
--- /dev/null
+++ b/src/share/api/remote-subnet6-list.json
@@ -0,0 +1,56 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command fetches a list of all IPv6 subnets from the configuration database."
+ ],
+ "cmd-comment": [
+ "The ``server-tags`` list is required for this command, and must not be empty. It may either contain one or multiple server tags as strings, or a single ``null`` value."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet6-list\",",
+ " \"arguments\": {",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet6-list",
+ "resp-comment": [
+ "The returned response contains a list of maps. Each map contains a subnet identifier, prefix, and shared network name to which the subnet belongs. If the subnet does not belong to a shared network, the name is null. The metadata includes database-specific information associated with the subnets. The returned list does not contain full subnet definitions; use ``remote-subnet6-get`` to fetch the full information about the selected subnets. If the command includes explicit server tags as strings (including the special server tag \"all\"), the list contains all subnets which are associated with any of the specified tags. A subnet is returned even if it is associated with multiple servers and only one of the specified tags matches. If the command includes the ``null`` value in the ``server-tags`` list, the response contains all subnets which are assigned to no servers (unassigned)."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv6 subnets found.\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <first subnet identifier>,",
+ " \"subnet\": <first subnet prefix>,",
+ " \"shared-network-name\": <shared network name> | null,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ " },",
+ " {",
+ " \"id\": <second subnet identifier>,",
+ " \"subnet\": <second subnet prefix>,",
+ " \"shared-network-name\": <shared network name> | null,",
+ " \"metadata\": {",
+ " \"server-tags\": [ <first server tag>, ... ]",
+ " }",
+ " }",
+ " ],",
+ " \"count\": 2",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/remote-subnet6-set.json b/src/share/api/remote-subnet6-set.json
new file mode 100644
index 0000000..c808822
--- /dev/null
+++ b/src/share/api/remote-subnet6-set.json
@@ -0,0 +1,44 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command creates or replaces an IPv6 subnet in the configuration database."
+ ],
+ "cmd-comment": [
+ "The provided list must contain exactly one subnet specification. The ``shared-network-name`` parameter is required for these commands; it associates the subnet with the shared network by its name. If the subnet must not belong to any shared network (a global subnet), the ``null`` value must be specified for the shared network name. The ``server-tags`` list is mandatory and must contain one or more server tags as strings to explicitly associate the subnet with one or more user-defined servers. The ``remote-subnet6-set`` command may include the special server tag \"all\" to associate the subnet with all servers."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"remote-subnet6-set\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": <subnet identifier>,",
+ " \"subnet\": <subnet prefix>,",
+ " \"shared-network-name\": <shared network name> | null,",
+ " <the rest of the subnet specification here>",
+ " }",
+ " ],",
+ " \"remote\": {",
+ " <specification of the database to connect to>",
+ " },",
+ " \"server-tags\": [ <first server tag>, <second server tag>, ... ]",
+ " }",
+ "}"
+ ],
+ "hook": "cb_cmds",
+ "name": "remote-subnet6-set",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 subnet successfully set.\",",
+ " \"arguments\": {",
+ " \"id\": <subnet identifier>,",
+ " \"subnet\": <subnet prefix>",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/reservation-add.json b/src/share/api/reservation-add.json
new file mode 100644
index 0000000..3eee6a0
--- /dev/null
+++ b/src/share/api/reservation-add.json
@@ -0,0 +1,50 @@
+{
+ "access": "write",
+ "avail": "1.2.0",
+ "brief": [
+ "This command adds a new host reservation. The reservation may include IPv4 addresses, IPv6 addresses, IPv6 prefixes, various identifiers, a class the client will be assigned to, DHCPv4 and DHCPv6 options, and more."
+ ],
+ "cmd-comment": [
+ "Note that ip-address, client-id, next-server, server-hostname, and boot-file-name are IPv4-specific. duid, ip-addresses, and prefixes are IPv6-specific."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"reservation-add\",",
+ " \"arguments\": {",
+ " \"reservation\": {",
+ " \"boot-file-name\": <string>,",
+ " \"comment\": <string>,",
+ " \"client-id\": <string>,",
+ " \"circuit-id\": <string>,",
+ " \"duid\": <string>,",
+ " \"flex-id\": <string>,",
+ " \"ip-address\": <string (IPv4 address)>,",
+ " \"ip-addresses\": [ <comma-separated strings> ],",
+ " \"hw-address\": <string>,",
+ " \"hostname\": <string>,",
+ " \"next-server\": <string (IPv4 address)>,",
+ " \"option-data\": [ <comma-separated structures defining options> ],",
+ " \"prefixes\": [ <comma-separated IPv6 prefixes> ],",
+ " \"client-classes\": [ <comma-separated strings> ],",
+ " \"server-hostname\": <string>,",
+ " \"subnet-id\": <integer>,",
+ " \"user-context\": <any valid JSON>",
+ " }",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"cmd-reservation-add\"/>",
+ "hook": "host_cmds",
+ "name": "reservation-add",
+ "resp-syntax": [
+ "",
+ "{",
+ " \"result\": <integer>,",
+ " \"text\": <string>",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/reservation-del.json b/src/share/api/reservation-del.json
new file mode 100644
index 0000000..61ec8d7
--- /dev/null
+++ b/src/share/api/reservation-del.json
@@ -0,0 +1,27 @@
+{
+ "access": "write",
+ "avail": "1.2.0",
+ "brief": [
+ "This command deletes an existing host reservation."
+ ],
+ "cmd-comment": [
+ "The host reservation can be identified by either the (subnet-id, ip-address) pair or a triplet of (subnet-id, identifier-type, identifier)."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"reservation-del\",",
+ " \"arguments\": {",
+ " \"subnet-id\": <integer>,",
+ " \"ip-address\": <string>,",
+ " \"identifier-type\": <one of 'hw-address', 'duid', 'circuit-id', 'client-id' and 'flex-id'>,",
+ " \"identifier\": <string>",
+ " }",
+ "}"
+ ],
+ "hook": "host_cmds",
+ "name": "reservation-del",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/reservation-get-all.json b/src/share/api/reservation-get-all.json
new file mode 100644
index 0000000..5e1eeb5
--- /dev/null
+++ b/src/share/api/reservation-get-all.json
@@ -0,0 +1,24 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command retrieves all host reservations for a specified subnet."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"reservation-get-all\",",
+ " \"arguments\": {",
+ " \"subnet-id\": <integer>",
+ " }",
+ "}"
+ ],
+ "hook": "host_cmds",
+ "name": "reservation-get-all",
+ "resp-comment": [
+ "The reservation-get-all command may result in very large responses."
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/reservation-get-by-hostname.json b/src/share/api/reservation-get-by-hostname.json
new file mode 100644
index 0000000..06d0e5c
--- /dev/null
+++ b/src/share/api/reservation-get-by-hostname.json
@@ -0,0 +1,25 @@
+{
+ "access": "read",
+ "avail": "1.7.1",
+ "brief": [
+ "This command retrieves all host reservations for a specified hostname and optionally a specified subnet."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"reservation-get-by-hostname\",",
+ " \"arguments\": {",
+ " \"hostname\": <hostname>,",
+ " \"subnet-id\": <integer>",
+ " }",
+ "}"
+ ],
+ "hook": "host_cmds",
+ "name": "reservation-get-by-hostname",
+ "resp-comment": [
+ "The reservation-get-by-hostname command may result in large responses."
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/reservation-get-by-id.json b/src/share/api/reservation-get-by-id.json
new file mode 100644
index 0000000..59958df
--- /dev/null
+++ b/src/share/api/reservation-get-by-id.json
@@ -0,0 +1,25 @@
+{
+ "access": "read",
+ "avail": "1.9.0",
+ "brief": [
+ "This command retrieves all host reservations for a specified identifier (type and value)."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"reservation-get-by-id\",",
+ " \"arguments\": {",
+ " \"identifier-type\": <one of 'hw-address', 'duid', 'circuit-id', 'client-id' and 'flex-id'>,",
+ " \"identifier\": <string>",
+ " }",
+ "}"
+ ],
+ "hook": "host_cmds",
+ "name": "reservation-get-by-id",
+ "resp-comment": [
+ "The reservation-get-by-id command may result in large responses."
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/reservation-get-page.json b/src/share/api/reservation-get-page.json
new file mode 100644
index 0000000..6be2b52
--- /dev/null
+++ b/src/share/api/reservation-get-page.json
@@ -0,0 +1,27 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command retrieves all host reservations or host reservations for a specified subnet by page."
+ ],
+ "cmd-comment": [
+ "The page size limit is mandatory. The subnet-id is optional since version 1.9.0. The source-index and from host-id are optional and default to 0. Values to use to load the next page are returned in responses in a next map."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"reservation-get-page\",",
+ " \"arguments\": {",
+ " \"subnet-id\": <integer>,",
+ " \"limit\": <integer>,",
+ " \"source-index\": <integer>,",
+ " \"from\": <integer>",
+ " }",
+ "}"
+ ],
+ "hook": "host_cmds",
+ "name": "reservation-get-page",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/reservation-get.json b/src/share/api/reservation-get.json
new file mode 100644
index 0000000..16b3f19
--- /dev/null
+++ b/src/share/api/reservation-get.json
@@ -0,0 +1,54 @@
+{
+ "access": "read",
+ "avail": "1.2.0",
+ "brief": [
+ "This command retrieves an existing host reservation."
+ ],
+ "cmd-comment": [
+ "The host reservation can be identified by either the (subnet-id, ip-address) pair or a triplet of (subnet-id, identifier-type, identifier)."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"reservation-get\",",
+ " \"arguments\": {",
+ " \"subnet-id\": <integer>,",
+ " \"identifier-type\": <one of 'hw-address', 'duid', 'circuit-id', 'client-id' and 'flex-id'>,",
+ " \"identifier\": <string>",
+ " }",
+ "}"
+ ],
+ "hook": "host_cmds",
+ "name": "reservation-get",
+ "resp-comment": [
+ "The arguments object appears only if a host is found. Many fields in the arguments object appear only if a specific field is set."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": <integer>,",
+ " \"text\": <string>,",
+ " \"arguments\": {",
+ " \"boot-file-name\": <string>,",
+ " \"comment\": <string>,",
+ " \"client-id\": <string>,",
+ " \"circuit-id\": <string>,",
+ " \"duid\": <string>,",
+ " \"flex-id\": <string>,",
+ " \"ip-address\": <string (IPv4 address)>,",
+ " \"ip-addresses\": [ <comma-separated strings> ],",
+ " \"hw-address\": <string>,",
+ " \"hostname\": <string>,",
+ " \"next-server\": <string (IPv4 address)>,",
+ " \"option-data\": [ <comma-separated structures defining options> ],",
+ " \"prefixes\": [ <comma-separated IPv6 prefixes> ],",
+ " \"client-classes\": [ <comma-separated strings> ],",
+ " \"server-hostname\": <string>,",
+ " \"subnet-id\": <integer>,",
+ " \"user-context\": <any valid JSON>",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/server-tag-get.json b/src/share/api/server-tag-get.json
new file mode 100644
index 0000000..c06204b
--- /dev/null
+++ b/src/share/api/server-tag-get.json
@@ -0,0 +1,30 @@
+{
+ "access": "read",
+ "avail": "1.6.0",
+ "brief": [
+ "This command returns the server tag used by the server. ",
+ "Server tag is essential configuration parameter in the Config Backend configuration.",
+ "This parameter is configured in the local config file.",
+ "This command does not take any parameters."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"server-tag-get\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-server-tag-get\"/>",
+ "name": "server-tag-get",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"arguments\": {",
+ " \"server-tag\": \"office1\"",
+ " }",
+ "}"
+ ],
+
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/shutdown.json b/src/share/api/shutdown.json
new file mode 100644
index 0000000..bc7bc9d
--- /dev/null
+++ b/src/share/api/shutdown.json
@@ -0,0 +1,26 @@
+{
+ "access": "write",
+ "avail": "1.0.0",
+ "brief": [
+ "This command instructs the server to initiate its shutdown procedure."
+ ],
+ "cmd-comment": [
+ "The server responds with a confirmation that the shutdown procedure has been initiated."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"shutdown\"",
+ " \"arguments\": {",
+ " \"exit-value\": 123",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-shutdown\"/>",
+ "name": "shutdown",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns",
+ "kea-ctrl-agent"
+ ]
+}
diff --git a/src/share/api/stat-lease4-get.json b/src/share/api/stat-lease4-get.json
new file mode 100644
index 0000000..5deb196
--- /dev/null
+++ b/src/share/api/stat-lease4-get.json
@@ -0,0 +1,38 @@
+{
+ "access": "read",
+ "avail": "1.4.0",
+ "brief": [
+ "This command fetches lease statistics for a range of known IPv4 subnets."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"stat-lease4-get\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-stat-lease4-get\"/>",
+ "hook": "stat_cmds",
+ "name": "stat-lease4-get",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"stat-lease4-get: 2 rows found\",",
+ " \"arguments\": {",
+ " \"result-set\": {",
+ " \"columns\": [ \"subnet-id\",",
+ " \"total-addresses\",",
+ " \"cumulative-assigned-addresses\",",
+ " \"assigned-addresses\",",
+ " \"declined-addresses\" ],",
+ " \"rows\": [",
+ " [ 10, 256, 200, 111, 0 ],",
+ " [ 20, 4098, 5000, 2034, 4 ]",
+ " ],",
+ " \"timestamp\": \"2018-05-04 15:03:37.000000\"",
+ " }",
+ " }",
+ " }"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/stat-lease6-get.json b/src/share/api/stat-lease6-get.json
new file mode 100644
index 0000000..9db6637
--- /dev/null
+++ b/src/share/api/stat-lease6-get.json
@@ -0,0 +1,38 @@
+{
+ "access": "read",
+ "avail": "1.4.0",
+ "brief": [
+ "This command fetches lease statistics for a range of known IPv6 subnets."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"stat-lease6-get\",",
+ " \"arguments\": {",
+ " \"subnet-id\" : 10",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-stat-lease6-get\"/>",
+ "hook": "stat_cmds",
+ "name": "stat-lease6-get",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"stat-lease6-get: 2 rows found\",",
+ " \"arguments\": {",
+ " \"result-set\": {",
+ " \"columns\": [ \"subnet-id\", \"total-nas\", \"cumulative-assigned-nas\", \"assigned-nas\", \"declined-nas\", \"total-pds\", \"cumulative-assigned-pds\", \"assigned-pds\" ],",
+ " \"rows\": [",
+ " [ 10, 4096, 3000, 2400, 3, 0, 0],",
+ " [ 20, 0, 0, 0, 1048, 500, 233 ],",
+ " [ 30, 256, 300, 60, 0, 1048, 15, 15 ]",
+ " ],",
+ " \"timestamp\": \"2018-05-04 15:03:37.000000\"",
+ " }",
+ " }",
+ " }"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/statistic-get-all.json b/src/share/api/statistic-get-all.json
new file mode 100644
index 0000000..df1eae3
--- /dev/null
+++ b/src/share/api/statistic-get-all.json
@@ -0,0 +1,64 @@
+{
+ "access": "read",
+ "avail": "1.0.0",
+ "brief": [
+ "This command retrieves all recorded statistics."
+ ],
+ "cmd-comment": [
+ "The server responds with the details of all recorded statistics, with a result of 0 indicating that it iterated over all statistics (even when the total number of statistics is zero)."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"statistic-get-all\",",
+ " \"arguments\": { }",
+ "}"
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"arguments\": {",
+ " \"cumulative-assigned-addresses\": [ [ 0, \"2022-02-11 17:54:17.487569\" ] ],",
+ " \"declined-addresses\": [ [ 0, \"2022-02-11 17:54:17.487555\" ] ],",
+ " \"pkt4-ack-received\": [ [ 0, \"2022-02-11 17:54:17.455233\" ] ],",
+ " \"pkt4-ack-sent\": [ [ 0, \"2022-02-11 17:54:17.455256\" ] ],",
+ " \"pkt4-decline-received\": [ [ 0, \"2022-02-11 17:54:17.455259\" ] ],",
+ " \"pkt4-discover-received\": [ [ 0, \"2022-02-11 17:54:17.455263\" ] ],",
+ " \"pkt4-inform-received\": [ [ 0, \"2022-02-11 17:54:17.455265\" ] ],",
+ " \"pkt4-nak-received\": [ [ 0, \"2022-02-11 17:54:17.455269\" ] ],",
+ " \"pkt4-nak-sent\": [ [ 0, \"2022-02-11 17:54:17.455271\" ] ],",
+ " \"pkt4-offer-received\": [ [ 0, \"2022-02-11 17:54:17.455274\" ] ],",
+ " \"pkt4-offer-sent\": [ [ 0, \"2022-02-11 17:54:17.455277\" ] ],",
+ " \"pkt4-parse-failed\": [ [ 0, \"2022-02-11 17:54:17.455280\" ] ],",
+ " \"pkt4-receive-drop\": [ [ 0, \"2022-02-11 17:54:17.455284\" ] ],",
+ " \"pkt4-received\": [ [ 0, \"2022-02-11 17:54:17.455287\" ] ],",
+ " \"pkt4-release-received\": [ [ 0, \"2022-02-11 17:54:17.455290\" ] ],",
+ " \"pkt4-request-received\": [ [ 0, \"2022-02-11 17:54:17.455293\" ] ],",
+ " \"pkt4-sent\": [ [ 0, \"2022-02-11 17:54:17.455296\" ] ],",
+ " \"pkt4-unknown-received\": [ [ 0, \"2022-02-11 17:54:17.455299\" ] ],",
+ " \"reclaimed-declined-addresses\": [ [ 0, \"2022-02-11 17:54:17.487559\" ] ],",
+ " \"reclaimed-leases\": [ [ 0, \"2022-02-11 17:54:17.487564\" ] ],",
+ " \"subnet[1].assigned-addresses\": [ [ 0, \"2022-02-11 17:54:17.487579\" ] ],",
+ " \"subnet[1].cumulative-assigned-addresses\": [ [ 0, \"2022-02-11 17:54:17.487528\" ] ],",
+ " \"subnet[1].declined-addresses\": [ [ 0, \"2022-02-11 17:54:17.487585\" ] ],",
+ " \"subnet[1].reclaimed-declined-addresses\": [ [ 0, \"2022-02-11 17:54:17.487595\" ] ],",
+ " \"subnet[1].reclaimed-leases\": [ [ 0, \"2022-02-11 17:54:17.487604\" ] ],",
+ " \"subnet[1].total-addresses\": [ [ 200, \"2022-02-11 17:54:17.487512\" ] ],",
+ " \"subnet[1].v4-reservation-conflicts\": [ [ 0, \"2022-02-11 17:54:17.487520\" ] ]",
+ " \"v4-allocation-fail\": [ [ 0, \"2022-02-11 17:54:17.455302\" ] ],",
+ " \"v4-allocation-fail-classes\": [ [ 0, \"2022-02-11 17:54:17.455306\" ] ],",
+ " \"v4-allocation-fail-no-pools\": [ [ 0, \"2022-02-11 17:54:17.455310\" ] ],",
+ " \"v4-allocation-fail-shared-network\": [ [ 0, \"2022-02-11 17:54:17.455319\" ] ],",
+ " \"v4-allocation-fail-subnet\": [ [ 0, \"2022-02-11 17:54:17.455323\" ] ]",
+ " \"v4-reservation-conflicts\": [ [ 0, \"2022-02-11 17:54:17.455330\" ] ]",
+ " }",
+ "}"
+ ],
+
+ "description": "See <xref linkend=\"command-statistic-get-all\"/>",
+ "name": "statistic-get-all",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns"
+ ]
+}
diff --git a/src/share/api/statistic-get.json b/src/share/api/statistic-get.json
new file mode 100644
index 0000000..b6c9cca
--- /dev/null
+++ b/src/share/api/statistic-get.json
@@ -0,0 +1,33 @@
+{
+ "access": "read",
+ "avail": "1.0.0",
+ "brief": [
+ "This command retrieves a single statistic. It takes a single string parameter called name that specifies the statistic name."
+ ],
+ "cmd-comment": [
+ "The server responds with the details of the requested statistic, with a result of 0 indicating success, and the specified statistic as the value of the \"arguments\" parameter."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"statistic-get\",",
+ " \"arguments\": {",
+ " \"name\": \"pkt4-received\"",
+ " }",
+ "}"
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"arguments\": {",
+ " \"pkt4-received\": [ [ \"first_value\", \"2019-07-30 10:11:19.498739\" ], [ \"second_value\", \"2019-07-30 10:11:19.498662\" ] ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-statistic-get\"/>",
+ "name": "statistic-get",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns"
+ ]
+}
diff --git a/src/share/api/statistic-remove-all.json b/src/share/api/statistic-remove-all.json
new file mode 100644
index 0000000..f1d9e13
--- /dev/null
+++ b/src/share/api/statistic-remove-all.json
@@ -0,0 +1,22 @@
+{
+ "access": "write",
+ "avail": "1.0.0",
+ "brief": [
+ "(Deprecated) This command deletes all statistics."
+ ],
+ "cmd-comment": [
+ "If the removal of all statistics is successful, the server responds with a status of 0, indicating success, and an empty parameters field. If an error is encountered, the server returns a status code of 1 (error) and the text field contains the error description."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"statistic-remove-all\",",
+ " \"arguments\": { }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-statistic-remove-all\"/>",
+ "name": "statistic-remove-all",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/statistic-remove.json b/src/share/api/statistic-remove.json
new file mode 100644
index 0000000..bece985
--- /dev/null
+++ b/src/share/api/statistic-remove.json
@@ -0,0 +1,24 @@
+{
+ "access": "write",
+ "avail": "1.0.0",
+ "brief": [
+ "This command deletes a single statistic. It takes a single string parameter called name that specifies the statistic name."
+ ],
+ "cmd-comment": [
+ "If the specific statistic is found and its removal is successful, the server responds with a status of 0, indicating success, and an empty parameters field. If an error is encountered (e.g. the requested statistic was not found), the server returns a status code of 1 (error) and the text field contains the error description."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"statistic-remove\",",
+ " \"arguments\": {",
+ " \"name\": \"pkt4-received\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-statistic-remove\"/>",
+ "name": "statistic-remove",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/statistic-reset-all.json b/src/share/api/statistic-reset-all.json
new file mode 100644
index 0000000..c8347de
--- /dev/null
+++ b/src/share/api/statistic-reset-all.json
@@ -0,0 +1,23 @@
+{
+ "access": "write",
+ "avail": "1.0.0",
+ "brief": [
+ "This command sets all statistics to their neutral values: 0 for integer, 0.0 for float, 0h0m0s0us for time duration, and \"\" for string type."
+ ],
+ "cmd-comment": [
+ "If the operation is successful, the server responds with a status of 0, indicating success, and an empty parameters field. If an error is encountered, the server returns a status code of 1 (error) and the text field contains the error description."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"statistic-reset-all\",",
+ " \"arguments\": { }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-statistic-reset-all\"/>",
+ "name": "statistic-reset-all",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns"
+ ]
+}
diff --git a/src/share/api/statistic-reset.json b/src/share/api/statistic-reset.json
new file mode 100644
index 0000000..8a758f6
--- /dev/null
+++ b/src/share/api/statistic-reset.json
@@ -0,0 +1,25 @@
+{
+ "access": "write",
+ "avail": "1.0.0",
+ "brief": [
+ "This command sets the specified statistic to its neutral value: 0 for integer, 0.0 for float, 0h0m0s0us for time duration, and \"\" for string type. It takes a single string parameter called name that specifies the statistic name."
+ ],
+ "cmd-comment": [
+ "If the specific statistic is found and the reset is successful, the server responds with a status of 0, indicating success, and an empty parameters field. If an error is encountered (e.g. the requested statistic was not found), the server returns a status code of 1 (error) and the text field contains the error description."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"statistic-reset\",",
+ " \"arguments\": {",
+ " \"name\": \"pkt4-received\"",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-statistic-reset\"/>",
+ "name": "statistic-reset",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns"
+ ]
+}
diff --git a/src/share/api/statistic-sample-age-set-all.json b/src/share/api/statistic-sample-age-set-all.json
new file mode 100644
index 0000000..83a60c1
--- /dev/null
+++ b/src/share/api/statistic-sample-age-set-all.json
@@ -0,0 +1,24 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command sets a time-based limit for all statistics. It takes a single integer parameter called duration."
+ ],
+ "cmd-comment": [
+ "The server responds with a message about successfully set limits for all statistics, with a result of 0 indicating success, and an empty parameters field. If an error is encountered, the server returns a status code of 1 (error) and the text field contains the error description."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"statistic-sample-age-set-all\",",
+ " \"arguments\": {",
+ " \"duration\": 1245",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-statistic-sample-age-set-all\"/>",
+ "name": "statistic-sample-age-set-all",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/statistic-sample-age-set.json b/src/share/api/statistic-sample-age-set.json
new file mode 100644
index 0000000..2091136
--- /dev/null
+++ b/src/share/api/statistic-sample-age-set.json
@@ -0,0 +1,25 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command sets a time-based limit for a single statistic. It takes two parameters: a string called name and an integer value called duration."
+ ],
+ "cmd-comment": [
+ "The server responds with a message about a successfully set limit for the given statistic, with a result of 0 indicating success, and an empty parameters field. If an error is encountered (e.g. the requested statistic was not found), the server returns a status code of 1 (error) and the text field contains the error description."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"statistic-sample-age-set\",",
+ " \"arguments\": {",
+ " \"name\": \"pkt4-received\",",
+ " \"duration\": 1245",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-statistic-sample-age-set\"/>",
+ "name": "statistic-sample-age-set",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/statistic-sample-count-set-all.json b/src/share/api/statistic-sample-count-set-all.json
new file mode 100644
index 0000000..f4bbe2a
--- /dev/null
+++ b/src/share/api/statistic-sample-count-set-all.json
@@ -0,0 +1,24 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command sets a size-based limit for all statistics. It takes a single integer parameter called max-samples."
+ ],
+ "cmd-comment": [
+ "The server responds with a message about successfully set limits for all statistics, with a result of 0 indicating success, and an empty parameters field. If an error is encountered, the server returns a status code of 1 (error) and the text field contains the error description."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"statistic-sample-count-set-all\",",
+ " \"arguments\": {",
+ " \"max-samples\": 100",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-statistic-sample-count-set-all\"/>",
+ "name": "statistic-sample-count-set-all",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/statistic-sample-count-set.json b/src/share/api/statistic-sample-count-set.json
new file mode 100644
index 0000000..1e8e17e
--- /dev/null
+++ b/src/share/api/statistic-sample-count-set.json
@@ -0,0 +1,25 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command sets a size-based limit for a single statistic. It takes two parameters: a string called name and an integer value called max-samples."
+ ],
+ "cmd-comment": [
+ "The server responds with a message about a successfully set limit for the given statistic, with a result of 0 indicating success, and an empty parameters field. If an error is encountered (e.g. the requested statistic was not found), the server returns a status code of 1 (error) and the text field contains the error description."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"statistic-sample-count-set\",",
+ " \"arguments\": {",
+ " \"name\": \"pkt4-received\",",
+ " \"max-samples\": 100",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-statistic-sample-count-set\"/>",
+ "name": "statistic-sample-count-set",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/status-get.json b/src/share/api/status-get.json
new file mode 100644
index 0000000..eea4acf
--- /dev/null
+++ b/src/share/api/status-get.json
@@ -0,0 +1,64 @@
+{
+ "access": "read",
+ "avail": "1.7.3",
+ "brief": [
+ "This command returns server's runtime information.",
+ "It takes no arguments."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"status-get\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-status-get\"/>",
+ "name": "status-get",
+ "resp-comment": [
+ "If the libdhcp_ha (High Availability) hooks library is loaded by the DHCP server receiving this command the response also includes the HA specific status information of the server receiving the command and its partner's status."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": <integer>,",
+ " \"arguments\": {",
+ " \"pid\": <integer>,",
+ " \"uptime\": <uptime in seconds>,",
+ " \"reload\": <time since reload in seconds>,",
+ " \"high-availability\": [",
+ " {",
+ " \"ha-mode\": <HA mode configured for this relationship>",
+ " \"ha-servers\": {",
+ " \"local\": {",
+ " \"role\": <role of this server as in the configuration file>,",
+ " \"scopes\": <list of scope names served by this server>,",
+ " \"state\": <HA state name of the server receiving the command>,",
+ " },",
+ " \"remote\": {",
+ " \"age\": <the age of the remote status in seconds>,",
+ " \"in-touch\": <indicates if this server communicated with remote>,",
+ " \"last-scopes\": <list of scopes served by partner>,",
+ " \"last-state\": <HA state name of the partner>,",
+ " \"role\": <partner role>",
+ " }",
+ " }",
+ " }",
+ " ],",
+ " \"multi-threading-enabled\": true,",
+ " \"thread-pool-size\": 4,",
+ " \"packet-queue-size\": 64,",
+ " \"packet-queue-statistics\": [ 1.2, 2.3, 3.4 ],",
+ " \"sockets\": {",
+ " \"errors\": [",
+ " <error received during the last attempt to open all sockets>",
+ " ]",
+ " \"status\": <ready, retrying, or failed>",
+ " }",
+ " }",
+ "}"
+ ],
+
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns",
+ "kea-ctrl-agent"
+ ]
+}
diff --git a/src/share/api/subnet4-add.json b/src/share/api/subnet4-add.json
new file mode 100644
index 0000000..7b1ea8b
--- /dev/null
+++ b/src/share/api/subnet4-add.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command creates and adds a new subnet to the existing server configuration. This operation has no impact on other subnets."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet4-add\",",
+ " \"arguments\": {",
+ " \"subnet4\": [ {",
+ " \"id\": 123,",
+ " \"subnet\": \"10.20.30.0/24\",",
+ " ...",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp69\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet4-add",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 subnet added\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 123,",
+ " \"subnet\": \"10.20.30.0/24\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/subnet4-del.json b/src/share/api/subnet4-del.json
new file mode 100644
index 0000000..f1583da
--- /dev/null
+++ b/src/share/api/subnet4-del.json
@@ -0,0 +1,35 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command removes a subnet from the server's configuration. This command has no effect on other configured subnets, but removing a subnet has certain implications which the server's administrator should be aware of."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet4-del\",",
+ " \"arguments\": {",
+ " \"id\": 123",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp71\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet4-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 subnet 192.0.2.0/24 (id 123) deleted\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 123,",
+ " \"subnet\": \"192.0.2.0/24\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/subnet4-delta-add.json b/src/share/api/subnet4-delta-add.json
new file mode 100644
index 0000000..fb66313
--- /dev/null
+++ b/src/share/api/subnet4-delta-add.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "2.1.7",
+ "brief": [
+ "This command updates (adds or overwrites) parts of a single subnet in the existing server configuration. This operation has no impact on other subnets."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet4-delta-add\",",
+ " \"arguments\": {",
+ " \"subnet4\": [ {",
+ " \"id\": 123,",
+ " \"subnet\": \"10.20.30.0/24\",",
+ " ...",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp69\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet4-delta-add",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 subnet updated\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 123,",
+ " \"subnet\": \"10.20.30.0/24\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/subnet4-delta-del.json b/src/share/api/subnet4-delta-del.json
new file mode 100644
index 0000000..16c60d7
--- /dev/null
+++ b/src/share/api/subnet4-delta-del.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "2.1.7",
+ "brief": [
+ "This command updates (removes) parts of a single subnet in the existing server configuration. This operation has no impact on other subnets."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet4-delta-del\",",
+ " \"arguments\": {",
+ " \"subnet4\": [ {",
+ " \"id\": 123,",
+ " \"subnet\": \"10.20.30.0/24\",",
+ " ...",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp69\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet4-delta-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 subnet updated\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 123,",
+ " \"subnet\": \"10.20.30.0/24\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/subnet4-get.json b/src/share/api/subnet4-get.json
new file mode 100644
index 0000000..ab293dd
--- /dev/null
+++ b/src/share/api/subnet4-get.json
@@ -0,0 +1,39 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command retrieves detailed information about the specified subnet. This command usually follows ``subnet4-list``, which discovers available subnets with their respective subnet identifiers and prefixes."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet4-get\",",
+ " \"arguments\": {",
+ " \"id\": 10",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp67\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet4-get",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"Info about IPv4 subnet 10.0.0.0/8 (id 10) returned\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"subnet\": \"10.0.0.0/8\",",
+ " \"id\": 1,",
+ " \"option-data\": [",
+ " ...",
+ " ],",
+ " ...",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/subnet4-list.json b/src/share/api/subnet4-list.json
new file mode 100644
index 0000000..dfea625
--- /dev/null
+++ b/src/share/api/subnet4-list.json
@@ -0,0 +1,39 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command lists all currently configured subnets. The subnets are returned in a brief format, i.e. a subnet identifier and subnet prefix are included for each subnet."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet4-list\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp65\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet4-list",
+ "resp-comment": [
+ "If no IPv4 subnets are found, an error code is returned along with the error description."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv4 subnets found\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 10,",
+ " \"subnet\": \"10.0.0.0/8\"",
+ " },",
+ " {",
+ " \"id\": 100,",
+ " \"subnet\": \"192.0.2.0/24\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/subnet4-update.json b/src/share/api/subnet4-update.json
new file mode 100644
index 0000000..f131b9e
--- /dev/null
+++ b/src/share/api/subnet4-update.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command updates (overwrites) a single subnet in the existing server configuration. This operation has no impact on other subnets."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet4-update\",",
+ " \"arguments\": {",
+ " \"subnet4\": [ {",
+ " \"id\": 123,",
+ " \"subnet\": \"10.20.30.0/24\",",
+ " ...",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp69\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet4-update",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv4 subnet updated\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 123,",
+ " \"subnet\": \"10.20.30.0/24\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp4"
+ ]
+}
diff --git a/src/share/api/subnet6-add.json b/src/share/api/subnet6-add.json
new file mode 100644
index 0000000..77418d5
--- /dev/null
+++ b/src/share/api/subnet6-add.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command creates and adds a new subnet to the existing server configuration. This operation has no impact on other subnets."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet6-add\",",
+ " \"arguments\": {",
+ " \"subnet6\": [ {",
+ " \"id\": 234,",
+ " \"subnet\": \"2001:db8:1::/64\",",
+ " ...",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp70\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet6-add",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 subnet added\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 234,",
+ " \"subnet\": \"2001:db8:1::/64\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/subnet6-del.json b/src/share/api/subnet6-del.json
new file mode 100644
index 0000000..3e355ae
--- /dev/null
+++ b/src/share/api/subnet6-del.json
@@ -0,0 +1,33 @@
+{
+ "access": "write",
+ "avail": "1.3.0",
+ "brief": [
+ "This command removes a subnet from the server's configuration. This command has no effect on other configured subnets, but removing a subnet has certain implications which the server's administrator should be aware of."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet6-del\",",
+ " \"arguments\": {",
+ " \"id\": 234",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp72\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet6-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 subnet 2001:db8:1::/64 (id 234) deleted\",",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 234,",
+ " \"subnet\": \"2001:db8:1::/64\"",
+ " }",
+ " ]",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/subnet6-delta-add.json b/src/share/api/subnet6-delta-add.json
new file mode 100644
index 0000000..0b48c99
--- /dev/null
+++ b/src/share/api/subnet6-delta-add.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "2.1.7",
+ "brief": [
+ "This command updates (adds or overwrites) parts of a single subnet in the existing server configuration. This operation has no impact on other subnets."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet6-delta-add\",",
+ " \"arguments\": {",
+ " \"subnet6\": [ {",
+ " \"id\": 234,",
+ " \"subnet\": \"2001:db8:1::/64\",",
+ " ...",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp70\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet6-delta-add",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 subnet updated\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 234,",
+ " \"subnet\": \"2001:db8:1::/64\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/subnet6-delta-del.json b/src/share/api/subnet6-delta-del.json
new file mode 100644
index 0000000..39118bf
--- /dev/null
+++ b/src/share/api/subnet6-delta-del.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "2.1.7",
+ "brief": [
+ "This command updates (removes) parts of a single subnet in the existing server configuration. This operation has no impact on other subnets."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet6-delta-del\",",
+ " \"arguments\": {",
+ " \"subnet6\": [ {",
+ " \"id\": 234,",
+ " \"subnet\": \"2001:db8:1::/64\",",
+ " ...",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp70\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet6-delta-del",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 subnet updated\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 234,",
+ " \"subnet\": \"2001:db8:1::/64\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/subnet6-get.json b/src/share/api/subnet6-get.json
new file mode 100644
index 0000000..956e401
--- /dev/null
+++ b/src/share/api/subnet6-get.json
@@ -0,0 +1,39 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command retrieves detailed information about the specified subnet. This command usually follows ``subnet6-list``, which discovers available subnets with their respective subnet identifiers and prefixes."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet6-get\",",
+ " \"arguments\": {",
+ " \"id\": 11",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp68\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet6-get",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"Info about IPv6 subnet 2001:db8:1::/64 (id 11) returned\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"subnet\": \"2001:db8:1::/64\",",
+ " \"id\": 1,",
+ " \"option-data\": [",
+ " ...",
+ " ],",
+ " ...",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/subnet6-list.json b/src/share/api/subnet6-list.json
new file mode 100644
index 0000000..7202ca7
--- /dev/null
+++ b/src/share/api/subnet6-list.json
@@ -0,0 +1,39 @@
+{
+ "access": "read",
+ "avail": "1.3.0",
+ "brief": [
+ "This command lists all currently configured subnets. The subnets are returned in a brief format, i.e. a subnet identifier and subnet prefix are included for each subnet."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet6-list\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp66\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet6-list",
+ "resp-comment": [
+ "If no IPv6 subnets are found, an error code is returned along with the error description."
+ ],
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"2 IPv6 subnets found\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 11,",
+ " \"subnet\": \"2001:db8:1::/64\"",
+ " },",
+ " {",
+ " \"id\": 233,",
+ " \"subnet\": \"3000::/16\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/subnet6-update.json b/src/share/api/subnet6-update.json
new file mode 100644
index 0000000..3d5725d
--- /dev/null
+++ b/src/share/api/subnet6-update.json
@@ -0,0 +1,39 @@
+{
+ "access": "write",
+ "avail": "1.6.0",
+ "brief": [
+ "This command updates (overwrites) a single subnet in the existing server configuration. This operation has no impact on other subnets."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"subnet6-update\",",
+ " \"arguments\": {",
+ " \"subnet6\": [ {",
+ " \"id\": 234,",
+ " \"subnet\": \"2001:db8:1::/64\",",
+ " ...",
+ " } ]",
+ " }",
+ "}"
+ ],
+ "description": "See <xref linkend=\"idp70\"/>",
+ "hook": "subnet_cmds",
+ "name": "subnet6-update",
+ "resp-syntax": [
+ "{",
+ " \"result\": 0,",
+ " \"text\": \"IPv6 subnet updated\",",
+ " \"arguments\": {",
+ " \"subnets\": [",
+ " {",
+ " \"id\": 234,",
+ " \"subnet\": \"2001:db8:1::/64\"",
+ " }",
+ " ]",
+ " }",
+ "}"
+ ],
+ "support": [
+ "kea-dhcp6"
+ ]
+}
diff --git a/src/share/api/version-get.json b/src/share/api/version-get.json
new file mode 100644
index 0000000..8dac1f7
--- /dev/null
+++ b/src/share/api/version-get.json
@@ -0,0 +1,20 @@
+{
+ "access": "read",
+ "avail": "1.2.0",
+ "brief": [
+ "This command returns extended information about the Kea version that is running. The returned string is the same as if Kea were run with the ``-V`` command-line option."
+ ],
+ "cmd-syntax": [
+ "{",
+ " \"command\": \"version-get\"",
+ "}"
+ ],
+ "description": "See <xref linkend=\"command-version-get\"/>",
+ "name": "version-get",
+ "support": [
+ "kea-dhcp4",
+ "kea-dhcp6",
+ "kea-dhcp-ddns",
+ "kea-ctrl-agent"
+ ]
+}
diff --git a/src/share/database/Makefile.am b/src/share/database/Makefile.am
new file mode 100644
index 0000000..7d7f88a
--- /dev/null
+++ b/src/share/database/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = scripts
diff --git a/src/share/database/Makefile.in b/src/share/database/Makefile.in
new file mode 100644
index 0000000..d552377
--- /dev/null
+++ b/src/share/database/Makefile.in
@@ -0,0 +1,704 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/share/database
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = scripts
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/share/database/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/share/database/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/share/database/scripts/Makefile.am b/src/share/database/scripts/Makefile.am
new file mode 100644
index 0000000..6602983
--- /dev/null
+++ b/src/share/database/scripts/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = mysql pgsql
diff --git a/src/share/database/scripts/Makefile.in b/src/share/database/scripts/Makefile.in
new file mode 100644
index 0000000..01e7f65
--- /dev/null
+++ b/src/share/database/scripts/Makefile.in
@@ -0,0 +1,704 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/share/database/scripts
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = mysql pgsql
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/share/database/scripts/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/share/database/scripts/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/share/database/scripts/mysql/Makefile.am b/src/share/database/scripts/mysql/Makefile.am
new file mode 100644
index 0000000..29ba228
--- /dev/null
+++ b/src/share/database/scripts/mysql/Makefile.am
@@ -0,0 +1,40 @@
+SUBDIRS = .
+
+mysqldir = ${datarootdir}/${PACKAGE_NAME}/scripts/mysql
+
+# non-executable
+mysql_DATA =
+mysql_DATA += dhcpdb_create.mysql
+mysql_DATA += dhcpdb_drop.mysql
+
+# executable
+mysql_SCRIPTS =
+mysql_SCRIPTS += upgrade_001.0_to_002.0.sh
+mysql_SCRIPTS += upgrade_002.0_to_003.0.sh
+mysql_SCRIPTS += upgrade_003.0_to_004.0.sh
+mysql_SCRIPTS += upgrade_004.0_to_004.1.sh
+mysql_SCRIPTS += upgrade_004.1_to_005.0.sh
+mysql_SCRIPTS += upgrade_005.0_to_005.1.sh
+mysql_SCRIPTS += upgrade_005.1_to_005.2.sh
+mysql_SCRIPTS += upgrade_005.2_to_006.0.sh
+mysql_SCRIPTS += upgrade_006.0_to_007.0.sh
+mysql_SCRIPTS += upgrade_007.0_to_008.0.sh
+mysql_SCRIPTS += upgrade_008.0_to_008.1.sh
+mysql_SCRIPTS += upgrade_008.1_to_008.2.sh
+mysql_SCRIPTS += upgrade_008.2_to_009.0.sh
+mysql_SCRIPTS += upgrade_009.0_to_009.1.sh
+mysql_SCRIPTS += upgrade_009.1_to_009.2.sh
+mysql_SCRIPTS += upgrade_009.2_to_009.3.sh
+mysql_SCRIPTS += upgrade_009.3_to_009.4.sh
+mysql_SCRIPTS += upgrade_009.4_to_009.5.sh
+mysql_SCRIPTS += upgrade_009.5_to_009.6.sh
+mysql_SCRIPTS += upgrade_009.6_to_010.0.sh
+mysql_SCRIPTS += upgrade_010_to_011.sh
+mysql_SCRIPTS += upgrade_011_to_012.sh
+mysql_SCRIPTS += upgrade_012_to_013.sh
+mysql_SCRIPTS += upgrade_013_to_014.sh
+mysql_SCRIPTS += wipe_data.sh
+
+DISTCLEANFILES = ${mysql_SCRIPTS}
+
+EXTRA_DIST = ${mysql_DATA}
diff --git a/src/share/database/scripts/mysql/Makefile.in b/src/share/database/scripts/mysql/Makefile.in
new file mode 100644
index 0000000..a85e34d
--- /dev/null
+++ b/src/share/database/scripts/mysql/Makefile.in
@@ -0,0 +1,904 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/share/database/scripts/mysql
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = upgrade_001.0_to_002.0.sh \
+ upgrade_002.0_to_003.0.sh upgrade_003.0_to_004.0.sh \
+ upgrade_004.0_to_004.1.sh upgrade_004.1_to_005.0.sh \
+ upgrade_005.0_to_005.1.sh upgrade_005.1_to_005.2.sh \
+ upgrade_005.2_to_006.0.sh upgrade_006.0_to_007.0.sh \
+ upgrade_007.0_to_008.0.sh upgrade_008.0_to_008.1.sh \
+ upgrade_008.1_to_008.2.sh upgrade_008.2_to_009.0.sh \
+ upgrade_009.0_to_009.1.sh upgrade_009.1_to_009.2.sh \
+ upgrade_009.2_to_009.3.sh upgrade_009.3_to_009.4.sh \
+ upgrade_009.4_to_009.5.sh upgrade_009.5_to_009.6.sh \
+ upgrade_009.6_to_010.0.sh upgrade_010_to_011.sh \
+ upgrade_011_to_012.sh upgrade_012_to_013.sh \
+ upgrade_013_to_014.sh wipe_data.sh
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(mysqldir)" "$(DESTDIR)$(mysqldir)"
+SCRIPTS = $(mysql_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+DATA = $(mysql_DATA)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/upgrade_001.0_to_002.0.sh.in \
+ $(srcdir)/upgrade_002.0_to_003.0.sh.in \
+ $(srcdir)/upgrade_003.0_to_004.0.sh.in \
+ $(srcdir)/upgrade_004.0_to_004.1.sh.in \
+ $(srcdir)/upgrade_004.1_to_005.0.sh.in \
+ $(srcdir)/upgrade_005.0_to_005.1.sh.in \
+ $(srcdir)/upgrade_005.1_to_005.2.sh.in \
+ $(srcdir)/upgrade_005.2_to_006.0.sh.in \
+ $(srcdir)/upgrade_006.0_to_007.0.sh.in \
+ $(srcdir)/upgrade_007.0_to_008.0.sh.in \
+ $(srcdir)/upgrade_008.0_to_008.1.sh.in \
+ $(srcdir)/upgrade_008.1_to_008.2.sh.in \
+ $(srcdir)/upgrade_008.2_to_009.0.sh.in \
+ $(srcdir)/upgrade_009.0_to_009.1.sh.in \
+ $(srcdir)/upgrade_009.1_to_009.2.sh.in \
+ $(srcdir)/upgrade_009.2_to_009.3.sh.in \
+ $(srcdir)/upgrade_009.3_to_009.4.sh.in \
+ $(srcdir)/upgrade_009.4_to_009.5.sh.in \
+ $(srcdir)/upgrade_009.5_to_009.6.sh.in \
+ $(srcdir)/upgrade_009.6_to_010.0.sh.in \
+ $(srcdir)/upgrade_010_to_011.sh.in \
+ $(srcdir)/upgrade_011_to_012.sh.in \
+ $(srcdir)/upgrade_012_to_013.sh.in \
+ $(srcdir)/upgrade_013_to_014.sh.in $(srcdir)/wipe_data.sh.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+mysqldir = ${datarootdir}/${PACKAGE_NAME}/scripts/mysql
+
+# non-executable
+mysql_DATA = dhcpdb_create.mysql dhcpdb_drop.mysql
+
+# executable
+mysql_SCRIPTS = upgrade_001.0_to_002.0.sh upgrade_002.0_to_003.0.sh \
+ upgrade_003.0_to_004.0.sh upgrade_004.0_to_004.1.sh \
+ upgrade_004.1_to_005.0.sh upgrade_005.0_to_005.1.sh \
+ upgrade_005.1_to_005.2.sh upgrade_005.2_to_006.0.sh \
+ upgrade_006.0_to_007.0.sh upgrade_007.0_to_008.0.sh \
+ upgrade_008.0_to_008.1.sh upgrade_008.1_to_008.2.sh \
+ upgrade_008.2_to_009.0.sh upgrade_009.0_to_009.1.sh \
+ upgrade_009.1_to_009.2.sh upgrade_009.2_to_009.3.sh \
+ upgrade_009.3_to_009.4.sh upgrade_009.4_to_009.5.sh \
+ upgrade_009.5_to_009.6.sh upgrade_009.6_to_010.0.sh \
+ upgrade_010_to_011.sh upgrade_011_to_012.sh \
+ upgrade_012_to_013.sh upgrade_013_to_014.sh wipe_data.sh
+DISTCLEANFILES = ${mysql_SCRIPTS}
+EXTRA_DIST = ${mysql_DATA}
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/share/database/scripts/mysql/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/share/database/scripts/mysql/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+upgrade_001.0_to_002.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_001.0_to_002.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_002.0_to_003.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_002.0_to_003.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_003.0_to_004.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_003.0_to_004.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_004.0_to_004.1.sh: $(top_builddir)/config.status $(srcdir)/upgrade_004.0_to_004.1.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_004.1_to_005.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_004.1_to_005.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_005.0_to_005.1.sh: $(top_builddir)/config.status $(srcdir)/upgrade_005.0_to_005.1.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_005.1_to_005.2.sh: $(top_builddir)/config.status $(srcdir)/upgrade_005.1_to_005.2.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_005.2_to_006.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_005.2_to_006.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_006.0_to_007.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_006.0_to_007.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_007.0_to_008.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_007.0_to_008.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_008.0_to_008.1.sh: $(top_builddir)/config.status $(srcdir)/upgrade_008.0_to_008.1.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_008.1_to_008.2.sh: $(top_builddir)/config.status $(srcdir)/upgrade_008.1_to_008.2.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_008.2_to_009.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_008.2_to_009.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_009.0_to_009.1.sh: $(top_builddir)/config.status $(srcdir)/upgrade_009.0_to_009.1.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_009.1_to_009.2.sh: $(top_builddir)/config.status $(srcdir)/upgrade_009.1_to_009.2.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_009.2_to_009.3.sh: $(top_builddir)/config.status $(srcdir)/upgrade_009.2_to_009.3.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_009.3_to_009.4.sh: $(top_builddir)/config.status $(srcdir)/upgrade_009.3_to_009.4.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_009.4_to_009.5.sh: $(top_builddir)/config.status $(srcdir)/upgrade_009.4_to_009.5.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_009.5_to_009.6.sh: $(top_builddir)/config.status $(srcdir)/upgrade_009.5_to_009.6.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_009.6_to_010.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_009.6_to_010.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_010_to_011.sh: $(top_builddir)/config.status $(srcdir)/upgrade_010_to_011.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_011_to_012.sh: $(top_builddir)/config.status $(srcdir)/upgrade_011_to_012.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_012_to_013.sh: $(top_builddir)/config.status $(srcdir)/upgrade_012_to_013.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_013_to_014.sh: $(top_builddir)/config.status $(srcdir)/upgrade_013_to_014.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+wipe_data.sh: $(top_builddir)/config.status $(srcdir)/wipe_data.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+install-mysqlSCRIPTS: $(mysql_SCRIPTS)
+ @$(NORMAL_INSTALL)
+ @list='$(mysql_SCRIPTS)'; test -n "$(mysqldir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(mysqldir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(mysqldir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n' \
+ -e 'h;s|.*|.|' \
+ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+ if (++n[d] == $(am__install_max)) { \
+ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+ else { print "f", d "/" $$4, $$1 } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(mysqldir)$$dir'"; \
+ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(mysqldir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-mysqlSCRIPTS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(mysql_SCRIPTS)'; test -n "$(mysqldir)" || exit 0; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 's,.*/,,;$(transform)'`; \
+ dir='$(DESTDIR)$(mysqldir)'; $(am__uninstall_files_from_dir)
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-mysqlDATA: $(mysql_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(mysql_DATA)'; test -n "$(mysqldir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(mysqldir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(mysqldir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(mysqldir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(mysqldir)" || exit $$?; \
+ done
+
+uninstall-mysqlDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(mysql_DATA)'; test -n "$(mysqldir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(mysqldir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(SCRIPTS) $(DATA)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(mysqldir)" "$(DESTDIR)$(mysqldir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-mysqlDATA install-mysqlSCRIPTS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-mysqlDATA uninstall-mysqlSCRIPTS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-mysqlDATA install-mysqlSCRIPTS install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-mysqlDATA \
+ uninstall-mysqlSCRIPTS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/share/database/scripts/mysql/dhcpdb_create.mysql b/src/share/database/scripts/mysql/dhcpdb_create.mysql
new file mode 100644
index 0000000..c4b0bdc
--- /dev/null
+++ b/src/share/database/scripts/mysql/dhcpdb_create.mysql
@@ -0,0 +1,5094 @@
+# Copyright (C) 2012-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# This is the Kea schema specification for MySQL.
+#
+# The schema is reasonably portable (with the exception of the engine
+# specification, which is MySQL-specific). Minor changes might be needed for
+# other databases.
+
+# To create the schema, either type the command:
+#
+# mysql -u <user> -p <password> <database> < dhcpdb_create.mysql
+#
+# ... at the command prompt, or log in to the MySQL database and at the 'mysql>'
+# prompt, issue the command:
+#
+# source dhcpdb_create.mysql
+#
+# This script is also called from kea-admin, see kea-admin db-init mysql
+#
+# Over time, Kea database schema will evolve. Each version is marked with
+# major.minor version. This file is organized sequentially, i.e. database
+# is initialized to 1.0, then upgraded to 2.0 etc. This may be somewhat
+# sub-optimal, but it ensues consistency with upgrade scripts. (It is much
+# easier to maintain init and upgrade scripts if they look the same).
+# Since initialization is done only once, it's performance is not an issue.
+
+# This line starts database initialization to 1.0.
+
+# Holds the IPv4 leases.
+CREATE TABLE lease4 (
+ address INT UNSIGNED PRIMARY KEY NOT NULL, # IPv4 address
+ hwaddr VARBINARY(20), # Hardware address
+ client_id VARBINARY(128), # Client ID
+ valid_lifetime INT UNSIGNED, # Length of the lease (seconds)
+ expire TIMESTAMP, # Expiration time of the lease
+ subnet_id INT UNSIGNED, # Subnet identification
+ fqdn_fwd BOOL, # Has forward DNS update been performed by a server
+ fqdn_rev BOOL, # Has reverse DNS update been performed by a server
+ hostname VARCHAR(255) # The FQDN of the client
+ ) ENGINE = INNODB;
+
+
+# Create search indexes for lease4 table
+# index by hwaddr and subnet_id
+CREATE INDEX lease4_by_hwaddr_subnet_id ON lease4 (hwaddr, subnet_id);
+
+# index by client_id and subnet_id
+CREATE INDEX lease4_by_client_id_subnet_id ON lease4 (client_id, subnet_id);
+
+# Holds the IPv6 leases.
+# N.B. The use of a VARCHAR for the address is temporary for development:
+# it will eventually be replaced by BINARY(16).
+CREATE TABLE lease6 (
+ address VARCHAR(39) PRIMARY KEY NOT NULL, # IPv6 address
+ duid VARBINARY(128), # DUID
+ valid_lifetime INT UNSIGNED, # Length of the lease (seconds)
+ expire TIMESTAMP, # Expiration time of the lease
+ subnet_id INT UNSIGNED, # Subnet identification
+ pref_lifetime INT UNSIGNED, # Preferred lifetime
+ lease_type TINYINT, # Lease type (see lease6_types
+ # table for possible values)
+ iaid INT UNSIGNED, # See Section 12 of RFC 8415
+ prefix_len TINYINT UNSIGNED, # For IA_PD only
+ fqdn_fwd BOOL, # Has forward DNS update been performed by a server
+ fqdn_rev BOOL, # Has reverse DNS update been performed by a server
+ hostname VARCHAR(255) # The FQDN of the client
+
+ ) ENGINE = INNODB;
+
+# Create search indexes for lease4 table
+# index by iaid, subnet_id, and duid
+CREATE INDEX lease6_by_iaid_subnet_id_duid ON lease6 (iaid, subnet_id, duid);
+
+# ... and a definition of lease6 types. This table is a convenience for
+# users of the database - if they want to view the lease table and use the
+# type names, they can join this table with the lease6 table.
+# Make sure those values match Lease6::LeaseType enum (see src/bin/dhcpsrv/
+# lease_mgr.h)
+CREATE TABLE lease6_types (
+ lease_type TINYINT PRIMARY KEY NOT NULL, # Lease type code.
+ name VARCHAR(5) # Name of the lease type
+ ) ENGINE = INNODB;
+
+START TRANSACTION;
+INSERT INTO lease6_types VALUES (0, 'IA_NA'); # Non-temporary v6 addresses
+INSERT INTO lease6_types VALUES (1, 'IA_TA'); # Temporary v6 addresses
+INSERT INTO lease6_types VALUES (2, 'IA_PD'); # Prefix delegations
+COMMIT;
+
+# Finally, the version of the schema. We start at 1.0 during development.
+# This table is only modified during schema upgrades. For historical reasons
+# (related to the names of the columns in the BIND 10 DNS database file), the
+# first column is called 'version' and not 'major'.
+CREATE TABLE schema_version (
+ version INT PRIMARY KEY NOT NULL, # Major version number
+ minor INT # Minor version number
+ ) ENGINE = INNODB;
+START TRANSACTION;
+INSERT INTO schema_version VALUES (1, 0);
+COMMIT;
+
+# This line concludes database initialization to version 1.0.
+
+# This line starts database upgrade to version 2.0.
+ALTER TABLE lease6
+ ADD COLUMN hwaddr varbinary(20), # Hardware/MAC address, typically only 6
+ # bytes is used, but some hardware (e.g.
+ # Infiniband) use up to 20.
+ ADD COLUMN hwtype smallint unsigned, # hardware type (16 bits)
+ ADD COLUMN hwaddr_source int unsigned; # Hardware source. See description
+ # of lease_hwaddr_source below.
+
+# Kea keeps track of the hardware/MAC address source, i.e. how the address
+# was obtained. Depending on the technique and your network topology, it may
+# be more or less trustworthy. This table is a convenience for
+# users of the database - if they want to view the lease table and use the
+# type names, they can join this table with the lease6 table. For details,
+# see constants defined in src/lib/dhcp/dhcp/pkt.h for detailed explanation.
+CREATE TABLE lease_hwaddr_source (
+ hwaddr_source INT PRIMARY KEY NOT NULL,
+ name VARCHAR(40)
+) ENGINE = INNODB;
+
+# Hardware address obtained from raw sockets
+INSERT INTO lease_hwaddr_source VALUES (1, 'HWADDR_SOURCE_RAW');
+
+# Hardware address converted from IPv6 link-local address with EUI-64
+INSERT INTO lease_hwaddr_source VALUES (2, 'HWADDR_SOURCE_IPV6_LINK_LOCAL');
+
+# Hardware address extracted from client-id (duid)
+INSERT INTO lease_hwaddr_source VALUES (4, 'HWADDR_SOURCE_DUID');
+
+# Hardware address extracted from client address relay option (RFC6939)
+INSERT INTO lease_hwaddr_source VALUES (8, 'HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION');
+
+# Hardware address extracted from remote-id option (RFC4649)
+INSERT INTO lease_hwaddr_source VALUES (16, 'HWADDR_SOURCE_REMOTE_ID');
+
+# Hardware address extracted from subscriber-id option (RFC4580)
+INSERT INTO lease_hwaddr_source VALUES (32, 'HWADDR_SOURCE_SUBSCRIBER_ID');
+
+# Hardware address extracted from docsis options
+INSERT INTO lease_hwaddr_source VALUES (64, 'HWADDR_SOURCE_DOCSIS');
+
+UPDATE schema_version SET version='2', minor='0';
+
+# This line concludes database upgrade to version 2.0.
+
+# This line starts database upgrade to version 3.0.
+# Upgrade extending MySQL schema with the ability to store hosts.
+
+CREATE TABLE IF NOT EXISTS hosts (
+ host_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ dhcp_identifier VARBINARY(128) NOT NULL,
+ dhcp_identifier_type TINYINT NOT NULL,
+ dhcp4_subnet_id INT UNSIGNED NULL,
+ dhcp6_subnet_id INT UNSIGNED NULL,
+ ipv4_address INT UNSIGNED NULL,
+ hostname VARCHAR(255) NULL,
+ dhcp4_client_classes VARCHAR(255) NULL,
+ dhcp6_client_classes VARCHAR(255) NULL,
+ PRIMARY KEY (host_id),
+ INDEX key_dhcp4_identifier_subnet_id (dhcp_identifier ASC , dhcp_identifier_type ASC),
+ INDEX key_dhcp6_identifier_subnet_id (dhcp_identifier ASC , dhcp_identifier_type ASC , dhcp6_subnet_id ASC)
+) ENGINE=INNODB;
+-- -----------------------------------------------------
+-- Table `ipv6_reservations`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS ipv6_reservations (
+ reservation_id INT NOT NULL AUTO_INCREMENT,
+ address VARCHAR(39) NOT NULL,
+ prefix_len TINYINT(3) UNSIGNED NOT NULL DEFAULT 128,
+ type TINYINT(4) UNSIGNED NOT NULL DEFAULT 0,
+ dhcp6_iaid INT UNSIGNED NULL,
+ host_id INT UNSIGNED NOT NULL,
+ PRIMARY KEY (reservation_id),
+ INDEX fk_ipv6_reservations_host_idx (host_id ASC),
+ CONSTRAINT fk_ipv6_reservations_Host FOREIGN KEY (host_id)
+ REFERENCES hosts (host_id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=INNODB;
+-- -----------------------------------------------------
+-- Table `dhcp4_options`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_options (
+ option_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ code TINYINT UNSIGNED NOT NULL,
+ value BLOB NULL,
+ formatted_value TEXT NULL,
+ space VARCHAR(128) NULL,
+ persistent TINYINT(1) NOT NULL DEFAULT 0,
+ dhcp_client_class VARCHAR(128) NULL,
+ dhcp4_subnet_id INT NULL,
+ host_id INT UNSIGNED NULL,
+ PRIMARY KEY (option_id),
+ UNIQUE INDEX option_id_UNIQUE (option_id ASC),
+ INDEX fk_options_host1_idx (host_id ASC),
+ CONSTRAINT fk_options_host1 FOREIGN KEY (host_id)
+ REFERENCES hosts (host_id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=INNODB;
+-- -----------------------------------------------------
+-- Table `dhcp6_options`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_options (
+ option_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ code INT UNSIGNED NOT NULL,
+ value BLOB NULL,
+ formatted_value TEXT NULL,
+ space VARCHAR(128) NULL,
+ persistent TINYINT(1) NOT NULL DEFAULT 0,
+ dhcp_client_class VARCHAR(128) NULL,
+ dhcp6_subnet_id INT NULL,
+ host_id INT UNSIGNED NULL,
+ PRIMARY KEY (option_id),
+ UNIQUE INDEX option_id_UNIQUE (option_id ASC),
+ INDEX fk_options_host1_idx (host_id ASC),
+ CONSTRAINT fk_options_host10 FOREIGN KEY (host_id)
+ REFERENCES hosts (host_id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=INNODB;
+
+DELIMITER $$
+CREATE TRIGGER host_BDEL BEFORE DELETE ON hosts FOR EACH ROW
+-- Edit trigger body code below this line. Do not edit lines above this one
+BEGIN
+DELETE FROM ipv6_reservations WHERE ipv6_reservations.host_id = OLD.host_id;
+END
+$$
+DELIMITER ;
+
+UPDATE schema_version
+SET version = '3', minor = '0';
+# This line concludes database upgrade to version 3.0.
+
+# This line starts database upgrade to version 4.0.
+# Upgrade extending MySQL schema with the state columns for lease tables.
+
+# Add state column to the lease4 table.
+ALTER TABLE lease4
+ ADD COLUMN state INT UNSIGNED DEFAULT 0;
+
+# Add state column to the lease6 table.
+ALTER TABLE lease6
+ ADD COLUMN state INT UNSIGNED DEFAULT 0;
+
+# Create indexes for querying leases in a given state and segregated
+# by the expiration time. One of the applications is to retrieve all
+# expired leases. However, these indexes can be also used to retrieve
+# leases in a given state regardless of the expiration time.
+CREATE INDEX lease4_by_state_expire ON lease4 (state ASC, expire ASC);
+CREATE INDEX lease6_by_state_expire ON lease6 (state ASC, expire ASC);
+
+# Create table holding mapping of the lease states to their names.
+# This is not used in queries from the DHCP server but rather in
+# direct queries from the lease database management tools.
+CREATE TABLE IF NOT EXISTS lease_state (
+ state INT UNSIGNED PRIMARY KEY NOT NULL,
+ name VARCHAR(64) NOT NULL
+) ENGINE=INNODB;
+
+# Insert currently defined state names.
+INSERT INTO lease_state VALUES (0, 'default');
+INSERT INTO lease_state VALUES (1, 'declined');
+INSERT INTO lease_state VALUES (2, 'expired-reclaimed');
+
+# Add a constraint that any state value added to the lease4 must
+# map to a value in the lease_state table.
+ALTER TABLE lease4
+ ADD CONSTRAINT fk_lease4_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+# Add a constraint that any state value added to the lease6 must
+# map to a value in the lease_state table.
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+# Add a constraint that lease type in the lease6 table must map
+# to a lease type defined in the lease6_types table.
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_type FOREIGN KEY (lease_type)
+ REFERENCES lease6_types (lease_type);
+
+# Modify the name of one of the HW address sources, and add a new one.
+UPDATE lease_hwaddr_source
+ SET name = 'HWADDR_SOURCE_DOCSIS_CMTS'
+ WHERE hwaddr_source = 64;
+
+INSERT INTO lease_hwaddr_source VALUES (128, 'HWADDR_SOURCE_DOCSIS_MODEM');
+
+# Add UNSIGNED to match with the lease6.
+ALTER TABLE lease_hwaddr_source
+ MODIFY COLUMN hwaddr_source INT UNSIGNED NOT NULL;
+
+# Add a constraint that non-null hwaddr_source in the lease6 table
+# must map to an entry in the lease_hwaddr_source.
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_hwaddr_source FOREIGN KEY (hwaddr_source)
+ REFERENCES lease_hwaddr_source (hwaddr_source);
+
+# Procedure that returns a result set containing the column names for lease4 dumps
+DROP PROCEDURE IF EXISTS lease4DumpHeader;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpHeader()
+BEGIN
+SELECT 'address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state';
+END $$
+DELIMITER ;
+
+# Procedure that returns a result set containing the data for lease4 dumps
+DROP PROCEDURE IF EXISTS lease4DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpData()
+BEGIN
+SELECT
+ INET_NTOA(l.address),
+ IFNULL(HEX(l.hwaddr), ''),
+ IFNULL(HEX(l.client_id), ''),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd,
+ l.fqdn_rev,
+ l.hostname,
+ s.name
+FROM
+ lease4 l
+ LEFT OUTER JOIN lease_state s on (l.state = s.state)
+ORDER BY l.address;
+END $$
+DELIMITER ;
+
+# Procedure that returns a result set containing the column names for lease6 dumps
+DROP PROCEDURE IF EXISTS lease6DumpHeader;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpHeader()
+BEGIN
+SELECT 'address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,hwtype,hwaddr_source,state';
+END $$
+DELIMITER ;
+
+# Procedure that returns a result set containing the data for lease6 dumps
+DROP PROCEDURE IF EXISTS lease6DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpData()
+BEGIN
+SELECT
+ l.address,
+ IFNULL(HEX(l.duid), ''),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ IFNULL(t.name, ''),
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd,
+ l.fqdn_rev,
+ l.hostname,
+ IFNULL(HEX(l.hwaddr), ''),
+ IFNULL(l.hwtype, ''),
+ IFNULL(h.name, ''),
+ IFNULL(s.name, '')
+FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state)
+ left outer join lease_hwaddr_source h on (l.hwaddr_source = h.hwaddr_source)
+ORDER BY l.address;
+END $$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '4', minor = '0';
+
+# This line concludes database upgrade to version 4.0.
+
+# In the event hardware address cannot be determined, we need to satisfy
+# foreign key constraint between lease6 and lease_hardware_source
+INSERT INTO lease_hwaddr_source VALUES (0, 'HWADDR_SOURCE_UNKNOWN');
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '4', minor = '1';
+
+# This line concludes database upgrade to version 4.1.
+
+# Update index used for searching DHCPv4 reservations by identifier and subnet id.
+# This index is now unique (to prevent duplicates) and includes DHCPv4 subnet
+# identifier.
+DROP INDEX key_dhcp4_identifier_subnet_id ON hosts;
+CREATE UNIQUE INDEX key_dhcp4_identifier_subnet_id ON hosts (dhcp_identifier ASC , dhcp_identifier_type ASC , dhcp4_subnet_id ASC);
+
+# Update index used for searching DHCPv6 reservations by identifier and subnet id.
+# This index is now unique to prevent duplicates.
+DROP INDEX key_dhcp6_identifier_subnet_id ON hosts;
+CREATE UNIQUE INDEX key_dhcp6_identifier_subnet_id ON hosts (dhcp_identifier ASC , dhcp_identifier_type ASC , dhcp6_subnet_id ASC);
+
+# Create index to search for reservations using IP address and subnet id.
+# This unique index guarantees that there is only one occurrence of the
+# particular IPv4 address for a given subnet.
+CREATE UNIQUE INDEX key_dhcp4_ipv4_address_subnet_id ON hosts (ipv4_address ASC , dhcp4_subnet_id ASC);
+
+# Create index to search for reservations using address/prefix and prefix
+# length.
+CREATE UNIQUE INDEX key_dhcp6_address_prefix_len ON ipv6_reservations (address ASC , prefix_len ASC);
+
+# Create a table mapping host identifiers to their names. Values in this
+# table are used as a foreign key in hosts table to guarantee that only
+# identifiers present in host_identifier_type table are used in hosts
+# table.
+CREATE TABLE IF NOT EXISTS host_identifier_type (
+ type TINYINT PRIMARY KEY NOT NULL, # Lease type code.
+ name VARCHAR(32) # Name of the lease type
+) ENGINE = INNODB;
+
+START TRANSACTION;
+INSERT INTO host_identifier_type VALUES (0, 'hw-address');
+INSERT INTO host_identifier_type VALUES (1, 'duid');
+INSERT INTO host_identifier_type VALUES (2, 'circuit-id');
+COMMIT;
+
+# Add a constraint that any identifier type value added to the hosts
+# must map to a value in the host_identifier_type table.
+ALTER TABLE hosts
+ ADD CONSTRAINT fk_host_identifier_type FOREIGN KEY (dhcp_identifier_type)
+ REFERENCES host_identifier_type (type);
+
+# Store DHCPv6 option code as 16-bit unsigned integer.
+ALTER TABLE dhcp6_options MODIFY code SMALLINT UNSIGNED NOT NULL;
+
+# Subnet identifier is unsigned.
+ALTER TABLE dhcp4_options MODIFY dhcp4_subnet_id INT UNSIGNED NULL;
+ALTER TABLE dhcp6_options MODIFY dhcp6_subnet_id INT UNSIGNED NULL;
+
+# Scopes associate DHCP options stored in dhcp4_options and
+# dhcp6_options tables with hosts, subnets, classes or indicate
+# that they are global options.
+CREATE TABLE IF NOT EXISTS dhcp_option_scope (
+ scope_id TINYINT UNSIGNED PRIMARY KEY NOT NULL,
+ scope_name VARCHAR(32)
+) ENGINE = INNODB;
+
+START TRANSACTION;
+INSERT INTO dhcp_option_scope VALUES (0, 'global');
+INSERT INTO dhcp_option_scope VALUES (1, 'subnet');
+INSERT INTO dhcp_option_scope VALUES (2, 'client-class');
+INSERT INTO dhcp_option_scope VALUES (3, 'host');
+COMMIT;
+
+# Add scopes into table holding DHCPv4 options
+ALTER TABLE dhcp4_options ADD COLUMN scope_id TINYINT UNSIGNED NOT NULL;
+ALTER TABLE dhcp4_options
+ ADD CONSTRAINT fk_dhcp4_option_scope FOREIGN KEY (scope_id)
+ REFERENCES dhcp_option_scope (scope_id);
+
+# Add scopes into table holding DHCPv6 options
+ALTER TABLE dhcp6_options ADD COLUMN scope_id TINYINT UNSIGNED NOT NULL;
+ALTER TABLE dhcp6_options
+ ADD CONSTRAINT fk_dhcp6_option_scope FOREIGN KEY (scope_id)
+ REFERENCES dhcp_option_scope (scope_id);
+
+# Add UNSIGNED to reservation_id
+ALTER TABLE ipv6_reservations
+ MODIFY reservation_id INT UNSIGNED NOT NULL AUTO_INCREMENT;
+
+# This line concludes database upgrade to version 7.0.
+
+# Add columns holding reservations for siaddr, sname and file fields
+# carried within DHCPv4 message.
+ALTER TABLE hosts ADD COLUMN dhcp4_next_server INT UNSIGNED NULL;
+ALTER TABLE hosts ADD COLUMN dhcp4_server_hostname VARCHAR(64) NULL;
+ALTER TABLE hosts ADD COLUMN dhcp4_boot_file_name VARCHAR(128) NULL;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '5', minor = '0';
+# This line concludes database upgrade to version 5.0.
+
+# Add missing 'client-id' and new 'flex-id' host identifier types.
+INSERT INTO host_identifier_type VALUES (3, 'client-id');
+INSERT INTO host_identifier_type VALUES (4, 'flex-id');
+
+# Recreate the trigger removing dependent host entries.
+DROP TRIGGER host_BDEL;
+
+DELIMITER $$
+CREATE TRIGGER host_BDEL BEFORE DELETE ON hosts FOR EACH ROW
+-- Edit trigger body code below this line. Do not edit lines above this one
+BEGIN
+DELETE FROM ipv6_reservations WHERE ipv6_reservations.host_id = OLD.host_id;
+DELETE FROM dhcp4_options WHERE dhcp4_options.host_id = OLD.host_id;
+DELETE FROM dhcp6_options WHERE dhcp6_options.host_id = OLD.host_id;
+END
+$$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '5', minor = '1';
+# This line concludes database upgrade to version 5.1.
+
+# Make subnet_id column types consistent with lease table columns
+ALTER TABLE dhcp4_options MODIFY dhcp4_subnet_id INT UNSIGNED;
+ALTER TABLE dhcp6_options MODIFY dhcp6_subnet_id INT UNSIGNED;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '5', minor = '2';
+
+# This line concludes database upgrade to version 5.2.
+
+# Add user context into table holding hosts
+ALTER TABLE hosts ADD COLUMN user_context TEXT NULL;
+
+# Add user contexts into tables holding DHCP options
+ALTER TABLE dhcp4_options ADD COLUMN user_context TEXT NULL;
+ALTER TABLE dhcp6_options ADD COLUMN user_context TEXT NULL;
+
+# Create index for searching leases by subnet identifier.
+CREATE INDEX lease4_by_subnet_id ON lease4 (subnet_id);
+
+# Create for searching leases by subnet identifier and lease type.
+CREATE INDEX lease6_by_subnet_id_lease_type ON lease6 (subnet_id, lease_type);
+
+# The index by iaid_subnet_id_duid is not the best choice because there are
+# cases when we don't specify subnet identifier while searching leases. The
+# index will be universal if the subnet_id is the right most column in the
+# index.
+DROP INDEX lease6_by_iaid_subnet_id_duid on lease6;
+CREATE INDEX lease6_by_duid_iaid_subnet_id ON lease6 (duid, iaid, subnet_id);
+
+# Create lease4_stat table
+CREATE TABLE lease4_stat (
+ subnet_id INT UNSIGNED NOT NULL,
+ state INT UNSIGNED NOT NULL,
+ leases BIGINT,
+ PRIMARY KEY (subnet_id, state)
+) ENGINE = INNODB;
+
+# Create stat_lease4_insert trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease4_insert AFTER INSERT ON lease4
+ FOR EACH ROW
+ BEGIN
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Update the state count if it exists
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ # Insert the state count record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat VALUES (NEW.subnet_id, NEW.state, 1);
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Create stat_lease4_update trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease4_update AFTER UPDATE ON lease4
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state != NEW.state THEN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the old state count if record exists
+ UPDATE lease4_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND state = OLD.state;
+ END IF;
+
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Increment the new state count if record exists
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ # Insert new state record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat VALUES (NEW.subnet_id, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Create stat_lease4_delete trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease4_delete AFTER DELETE ON lease4
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the state count if record exists
+ UPDATE lease4_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND OLD.state = state;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Create lease6_stat table
+CREATE TABLE lease6_stat (
+ subnet_id INT UNSIGNED NOT NULL,
+ lease_type INT UNSIGNED NOT NULL,
+ state INT UNSIGNED NOT NULL,
+ leases BIGINT,
+ PRIMARY KEY (subnet_id, lease_type, state)
+) ENGINE = INNODB;
+
+# Create stat_lease6_insert trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease6_insert AFTER INSERT ON lease6
+ FOR EACH ROW
+ BEGIN
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Update the state count if it exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE
+ subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ # Insert the state count record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat
+ VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Create stat_lease6_update trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease6_update AFTER UPDATE ON lease6
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state != NEW.state THEN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the old state count if record exists
+ UPDATE lease6_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ # Insert new state record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat
+ VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Create stat_lease6_delete trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease6_delete AFTER DELETE ON lease6
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the state count if record exists
+ UPDATE lease6_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '6', minor = '0';
+
+# This line concludes database upgrade to version 6.0.
+
+# Add user context into tables holding leases
+ALTER TABLE lease4 ADD COLUMN user_context TEXT NULL;
+ALTER TABLE lease6 ADD COLUMN user_context TEXT NULL;
+
+DROP PROCEDURE IF EXISTS lease4DumpHeader;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpHeader()
+BEGIN
+SELECT 'address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context';
+END $$
+DELIMITER ;
+
+# Procedure that returns a result set containing the data for lease4 dumps
+DROP PROCEDURE IF EXISTS lease4DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpData()
+BEGIN
+SELECT
+ INET_NTOA(l.address),
+ IFNULL(HEX(l.hwaddr), ''),
+ IFNULL(HEX(l.client_id), ''),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd,
+ l.fqdn_rev,
+ l.hostname,
+ s.name,
+ IFNULL(l.user_context, '')
+FROM
+ lease4 l
+ LEFT OUTER JOIN lease_state s on (l.state = s.state)
+ORDER BY l.address;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6DumpHeader;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpHeader()
+BEGIN
+SELECT 'address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,hwtype,hwaddr_source,state,user_context';
+END $$
+DELIMITER ;
+
+# Procedure that returns a result set containing the data for lease6 dumps
+DROP PROCEDURE IF EXISTS lease6DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpData()
+BEGIN
+SELECT
+ l.address,
+ IFNULL(HEX(l.duid), ''),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ IFNULL(t.name, ''),
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd,
+ l.fqdn_rev,
+ l.hostname,
+ IFNULL(HEX(l.hwaddr), ''),
+ IFNULL(l.hwtype, ''),
+ IFNULL(h.name, ''),
+ IFNULL(s.name, ''),
+ IFNULL(l.user_context, '')
+FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state)
+ left outer join lease_hwaddr_source h on (l.hwaddr_source = h.hwaddr_source)
+ORDER BY l.address;
+END $$
+DELIMITER ;
+
+# Create logs table (logs table is used by forensic logging hook library)
+CREATE TABLE logs (
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, # creation timestamp
+ address VARCHAR(43) NULL, # address or prefix
+ log TEXT NOT NULL # the log itself
+ ) ENGINE = INNODB;
+
+# Create search index
+CREATE INDEX timestamp_index ON logs (timestamp);
+
+#add auth key for reconfiguration
+ALTER TABLE hosts
+ ADD COLUMN auth_key VARCHAR(16) NULL;
+
+
+# Add scope for shared network specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(4, "shared-network");
+
+# Add scope for pool specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(5, "pool");
+
+# Add scope for PD pool specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(6, "pd-pool");
+
+-- -----------------------------------------------------
+-- Table `modification`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS modification (
+ id TINYINT(3) NOT NULL,
+ modification_type VARCHAR(32) NOT NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB;
+
+INSERT INTO modification(id, modification_type)
+ VALUES(0, "create");
+
+INSERT INTO modification(id, modification_type)
+ VALUES(1, "update");
+
+INSERT INTO modification(id, modification_type)
+ VALUES(2, "delete");
+
+-- -----------------------------------------------------
+-- Table `dhcp4_server`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_server (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ tag VARCHAR(64) NOT NULL,
+ description TEXT,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY dhcp4_server_tag_UNIQUE (tag),
+ KEY key_dhcp4_server_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+# Special server entry meaning "all servers". This refers to
+# the configuration entries owned by all servers.
+INSERT INTO dhcp4_server(id, tag, description, modification_ts)
+ VALUES(1, "all", "special type: all servers", NOW());
+
+-- -----------------------------------------------------
+-- Table `dhcp4_audit`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_audit (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ object_type VARCHAR(256) NOT NULL,
+ object_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_type TINYINT(1) NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ log_message TEXT,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_audit_by_modification_ts (modification_ts),
+ KEY fk_dhcp4_audit_modification_type (modification_type),
+ CONSTRAINT fk_dhcp4_audit_modification_type FOREIGN KEY (modification_type)
+ REFERENCES modification (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+
+-- -----------------------------------------------------
+-- Table `dhcp4_global_parameter`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_global_parameter (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ value LONGTEXT NOT NULL,
+ modification_ts timestamp NOT NULL,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_global_parameter_modification_ts (modification_ts),
+ KEY key_dhcp4_global_parameter_name (name)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp4_global_parameter_server`
+-- M-to-M cross-reference between global parameters and
+-- servers
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_global_parameter_server (
+ parameter_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (parameter_id, server_id),
+ KEY fk_dhcp4_global_parameter_server_server_id (server_id),
+ KEY key_dhcp4_global_parameter_server (modification_ts),
+ CONSTRAINT fk_dhcp4_global_parameter_server_parameter_id FOREIGN KEY (parameter_id)
+ REFERENCES dhcp4_global_parameter (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_global_parameter_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp4_option_def`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_option_def (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ code SMALLINT UNSIGNED NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ space VARCHAR(128) NOT NULL,
+ type TINYINT UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ is_array TINYINT(1) NOT NULL,
+ encapsulate VARCHAR(128) NOT NULL,
+ record_types VARCHAR(512) DEFAULT NULL,
+ user_context LONGTEXT,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_option_def_modification_ts (modification_ts),
+ KEY key_dhcp4_option_def_code_space (code, space)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp4_option_def_server`
+-- M-to-M cross-reference between option definitions and
+-- servers
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_option_def_server (
+ option_def_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (option_def_id, server_id),
+ KEY fk_dhcp4_option_def_server_server_id_idx (server_id),
+ KEY key_dhcp4_option_def_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp4_option_def_server_option_def_id FOREIGN KEY (option_def_id)
+ REFERENCES dhcp4_option_def (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_option_def_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp4_shared_network`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_shared_network (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ match_client_id TINYINT(1) NOT NULL DEFAULT '1',
+ modification_ts TIMESTAMP NOT NULL,
+ rebind_timer INT(10) DEFAULT NULL,
+ relay LONGTEXT,
+ renew_timer INT(10) DEFAULT NULL,
+ require_client_classes LONGTEXT DEFAULT NULL,
+ reservation_mode TINYINT(3) NOT NULL DEFAULT '3',
+ user_context LONGTEXT,
+ valid_lifetime INT(10) DEFAULT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp4_shared_network_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp4_shared_network_server`
+-- M-to-M cross-reference between shared networks and
+-- servers
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_shared_network_server (
+ shared_network_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (shared_network_id, server_id),
+ KEY key_dhcp4_shared_network_server_modification_ts (modification_ts),
+ KEY fk_dhcp4_shared_network_server_server_id (server_id),
+ CONSTRAINT fk_dhcp4_shared_network_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_shared_network_server_shared_network_id FOREIGN KEY (shared_network_id)
+ REFERENCES dhcp4_shared_network (id) ON DELETE CASCADE ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp4_subnet`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_subnet (
+ subnet_id INT(10) UNSIGNED NOT NULL,
+ subnet_prefix VARCHAR(32) NOT NULL,
+ 4o6_interface VARCHAR(128) DEFAULT NULL,
+ 4o6_interface_id VARCHAR(128) DEFAULT NULL,
+ 4o6_subnet VARCHAR(64) DEFAULT NULL,
+ boot_file_name VARCHAR(512) DEFAULT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ match_client_id TINYINT(1) NOT NULL DEFAULT '1',
+ modification_ts TIMESTAMP NOT NULL,
+ next_server INT(10) UNSIGNED DEFAULT NULL,
+ rebind_timer INT(10) DEFAULT NULL,
+ relay LONGTEXT,
+ renew_timer INT(10) DEFAULT NULL,
+ require_client_classes LONGTEXT DEFAULT NULL,
+ reservation_mode TINYINT(3) NOT NULL DEFAULT '3',
+ server_hostname VARCHAR(512) DEFAULT NULL,
+ shared_network_name VARCHAR(128) DEFAULT NULL,
+ user_context LONGTEXT,
+ valid_lifetime INT(10) DEFAULT NULL,
+ PRIMARY KEY (subnet_id),
+ UNIQUE KEY subnet4_subnet_prefix (subnet_prefix),
+ KEY fk_dhcp4_subnet_shared_network (shared_network_name),
+ KEY key_dhcp4_subnet_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp4_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network (name)
+ ON DELETE SET NULL ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp4_pool`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_pool (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ start_address INT(10) UNSIGNED NOT NULL,
+ end_address INT(10) UNSIGNED NOT NULL,
+ subnet_id INT(10) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_pool_modification_ts (modification_ts),
+ KEY fk_dhcp4_pool_subnet_id (subnet_id),
+ CONSTRAINT fk_dhcp4_pool_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp4_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp4_subnet_server`
+-- M-to-M cross-reference between subnets and servers
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_subnet_server (
+ subnet_id INT(10) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (subnet_id,server_id),
+ KEY fk_dhcp4_subnet_server_server_id_idx (server_id),
+ KEY key_dhcp4_subnet_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp4_subnet_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_subnet_server_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp4_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+
+# Modify the primary key to BINGINT as other tables have.
+ALTER TABLE dhcp4_options MODIFY option_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+# Add configuration backend specific columns.
+ALTER TABLE dhcp4_options
+ ADD COLUMN shared_network_name VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN pool_id BIGINT(20) UNSIGNED DEFAULT NULL,
+ ADD COLUMN modification_ts TIMESTAMP NOT NULL;
+
+-- -----------------------------------------------------
+-- Table `dhcp4_options_server`
+-- M-to-M cross-reference between options and servers
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_options_server (
+ option_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (option_id, server_id),
+ KEY fk_dhcp4_options_server_server_id (server_id),
+ KEY key_dhcp4_options_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp4_options_server_option_id FOREIGN KEY (option_id)
+ REFERENCES dhcp4_options (option_id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_options_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create trigger which removes pool specific options upon removal of
+# the pool.
+DELIMITER $$
+CREATE TRIGGER dhcp4_pool_BDEL BEFORE DELETE ON dhcp4_pool FOR EACH ROW
+-- Edit trigger body code below this line. Do not edit lines above this one
+BEGIN
+DELETE FROM dhcp4_options WHERE scope_id = 5 AND pool_id = OLD.id;
+END
+$$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_server`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_server (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ tag VARCHAR(64) NOT NULL,
+ description TEXT,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY dhcp6_server_tag_UNIQUE (tag),
+ KEY key_dhcp6_server_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+# Special server entry meaning "all servers". This refers to
+# the configuration entries owned by all servers.
+INSERT INTO dhcp6_server(id, tag, description, modification_ts)
+ VALUES(1, "all", "special type: all servers", NOW());
+
+-- -----------------------------------------------------
+-- Table `dhcp6_audit`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_audit (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ object_type VARCHAR(256) NOT NULL,
+ object_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_type TINYINT(1) NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ log_message TEXT,
+ PRIMARY KEY (id),
+ KEY key_dhcp6_audit_modification_ts (modification_ts),
+ KEY fk_dhcp6_audit_modification_type (modification_type),
+ CONSTRAINT fk_dhcp6_audit_modification_type FOREIGN KEY (modification_type)
+ REFERENCES modification (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_global_parameter`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_global_parameter (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ value LONGTEXT NOT NULL,
+ modification_ts timestamp NOT NULL,
+ PRIMARY KEY (id),
+ KEY key_dhcp6_global_parameter_modification_ts (modification_ts),
+ KEY key_dhcp6_global_parameter_name (name)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_global_parameter_server`
+-- M-to-M cross-reference between global parameters and
+-- servers
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_global_parameter_server (
+ parameter_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (parameter_id, server_id),
+ KEY fk_dhcp6_global_parameter_server_server_id (server_id),
+ KEY key_dhcp6_global_parameter_server (modification_ts),
+ CONSTRAINT fk_dhcp6_global_parameter_server_parameter_id FOREIGN KEY (parameter_id)
+ REFERENCES dhcp6_global_parameter (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_global_parameter_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_option_def`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_option_def (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ code SMALLINT UNSIGNED NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ space VARCHAR(128) NOT NULL,
+ type TINYINT UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ is_array TINYINT(1) NOT NULL,
+ encapsulate VARCHAR(128) NOT NULL,
+ record_types VARCHAR(512) DEFAULT NULL,
+ user_context LONGTEXT,
+ PRIMARY KEY (id),
+ KEY key_dhcp6_option_def_modification_ts (modification_ts),
+ KEY key_dhcp6_option_def_code_space (code, space)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_option_def_server`
+-- M-to-M cross-reference between option definitions and
+-- servers
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_option_def_server (
+ option_def_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (option_def_id, server_id),
+ KEY fk_dhcp6_option_def_server_server_id_idx (server_id),
+ KEY key_dhcp6_option_def_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_option_def_server_option_def_id FOREIGN KEY (option_def_id)
+ REFERENCES dhcp6_option_def (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_option_def_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_shared_network`
+-- -----------------------------------------------------
+CREATE TABLE dhcp6_shared_network (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ preferred_lifetime INT(10) DEFAULT NULL,
+ rapid_commit TINYINT(1) NOT NULL DEFAULT '1',
+ rebind_timer INT(10) DEFAULT NULL,
+ relay LONGTEXT DEFAULT NULL,
+ renew_timer INT(10) DEFAULT NULL,
+ require_client_classes LONGTEXT,
+ reservation_mode TINYINT(3) NOT NULL DEFAULT '3',
+ user_context LONGTEXT,
+ valid_lifetime INT(10) DEFAULT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp6_shared_network_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_shared_network_server`
+-- M-to-M cross-reference between shared networks and
+-- servers
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_shared_network_server (
+ shared_network_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ KEY key_dhcp6_shared_network_server_modification_ts (modification_ts),
+ KEY fk_dhcp6_shared_network_server_server_id_idx (server_id),
+ KEY fk_dhcp6_shared_network_server_shared_network_id (shared_network_id),
+ CONSTRAINT fk_dhcp6_shared_network_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_shared_network_server_shared_network_id FOREIGN KEY (shared_network_id)
+ REFERENCES dhcp6_shared_network (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_subnet`
+-- -----------------------------------------------------
+CREATE TABLE dhcp6_subnet (
+ subnet_id INT(10) UNSIGNED NOT NULL,
+ subnet_prefix VARCHAR(64) NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ preferred_lifetime INT(10) DEFAULT NULL,
+ rapid_commit TINYINT(1) NOT NULL DEFAULT '1',
+ rebind_timer INT(10) DEFAULT NULL,
+ relay LONGTEXT DEFAULT NULL,
+ renew_timer INT(10) DEFAULT NULL,
+ require_client_classes LONGTEXT,
+ reservation_mode TINYINT(3) NOT NULL DEFAULT '3',
+ shared_network_name VARCHAR(128) DEFAULT NULL,
+ user_context LONGTEXT,
+ valid_lifetime INT(10) DEFAULT NULL,
+ PRIMARY KEY (subnet_id),
+ UNIQUE KEY subnet_prefix_UNIQUE (subnet_prefix),
+ KEY subnet6_subnet_prefix (subnet_prefix),
+ KEY fk_dhcp6_subnet_shared_network (shared_network_name),
+ KEY key_dhcp6_subnet_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network (name)
+ ON DELETE SET NULL ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_subnet_server`
+-- M-to-M cross-reference between subnets and servers
+-- -----------------------------------------------------
+CREATE TABLE dhcp6_subnet_server (
+ subnet_id INT(10) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (subnet_id, server_id),
+ KEY fk_dhcp6_subnet_server_server_id (server_id),
+ KEY key_dhcp6_subnet_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_subnet_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_subnet_server_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_pd_pool`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_pd_pool (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ prefix VARCHAR(45) NOT NULL,
+ prefix_length TINYINT(3) NOT NULL,
+ delegated_prefix_length TINYINT(3) NOT NULL,
+ dhcp6_subnet_id INT(10) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ KEY fk_dhcp6_pd_pool_subnet_id (dhcp6_subnet_id),
+ KEY key_dhcp6_pd_pool_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_pd_pool_subnet_id FOREIGN KEY (dhcp6_subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_pool`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_pool (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ start_address VARCHAR(45) NOT NULL,
+ end_address VARCHAR(45) NOT NULL,
+ dhcp6_subnet_id INT(10) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ KEY fk_dhcp6_pool_subnet_id (dhcp6_subnet_id),
+ KEY key_dhcp6_pool_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_pool_subnet_id FOREIGN KEY (dhcp6_subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB;
+
+# Modify the primary key to BINGINT as other tables have.
+ALTER TABLE dhcp6_options MODIFY option_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+# Add configuration backend specific columns.
+ALTER TABLE dhcp6_options
+ ADD COLUMN shared_network_name VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN pool_id BIGINT(20) UNSIGNED DEFAULT NULL,
+ ADD COLUMN pd_pool_id BIGINT(20) UNSIGNED DEFAULT NULL,
+ ADD COLUMN modification_ts TIMESTAMP NOT NULL;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_options_server`
+-- M-to-M cross-reference between options and servers
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_options_server (
+ option_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (option_id, server_id),
+ KEY fk_dhcp6_options_server_server_id_idx (server_id),
+ KEY key_dhcp6_options_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_options_server_option_id FOREIGN KEY (option_id)
+ REFERENCES dhcp6_options (option_id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_options_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create trigger which removes pool specific options upon removal of
+# the pool.
+DELIMITER $$
+CREATE TRIGGER dhcp6_pool_BDEL BEFORE DELETE ON dhcp6_pool FOR EACH ROW
+-- Edit trigger body code below this line. Do not edit lines above this one
+BEGIN
+DELETE FROM dhcp6_options WHERE scope_id = 5 AND pool_id = OLD.id;
+END
+$$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '7', minor = '0';
+
+# This line concludes database upgrade to version 7.0.
+
+
+ALTER TABLE dhcp4_options
+ MODIFY COLUMN modification_ts TIMESTAMP NOT NULL
+ DEFAULT CURRENT_TIMESTAMP;
+
+ALTER TABLE dhcp6_options
+ MODIFY COLUMN modification_ts TIMESTAMP NOT NULL
+ DEFAULT CURRENT_TIMESTAMP;
+
+ALTER TABLE dhcp4_subnet
+ ADD COLUMN authoritative TINYINT(1) DEFAULT NULL,
+ ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL,
+ ADD COLUMN t1_percent FLOAT DEFAULT NULL,
+ ADD COLUMN t2_percent FLOAT DEFAULT NULL;
+
+ALTER TABLE dhcp4_subnet
+ MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL;
+
+ALTER TABLE dhcp4_subnet
+ MODIFY COLUMN match_client_id TINYINT(1) DEFAULT NULL;
+
+ALTER TABLE dhcp4_shared_network
+ ADD COLUMN authoritative TINYINT(1) DEFAULT NULL,
+ ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL,
+ ADD COLUMN t1_percent FLOAT DEFAULT NULL,
+ ADD COLUMN t2_percent FLOAT DEFAULT NULL,
+ ADD COLUMN boot_file_name VARCHAR(512) DEFAULT NULL,
+ ADD COLUMN next_server INT(10) UNSIGNED DEFAULT NULL,
+ ADD COLUMN server_hostname VARCHAR(512) DEFAULT NULL;
+
+ALTER TABLE dhcp4_shared_network
+ MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL;
+
+ALTER TABLE dhcp4_shared_network
+ MODIFY COLUMN match_client_id TINYINT(1) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL,
+ ADD COLUMN t1_percent FLOAT DEFAULT NULL,
+ ADD COLUMN t2_percent FLOAT DEFAULT NULL,
+ ADD COLUMN interface_id VARBINARY(128) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ MODIFY COLUMN rapid_commit TINYINT(1) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL,
+ ADD COLUMN t1_percent FLOAT DEFAULT NULL,
+ ADD COLUMN t2_percent FLOAT DEFAULT NULL,
+ ADD COLUMN interface_id VARBINARY(128) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ MODIFY COLUMN rapid_commit TINYINT(1) DEFAULT NULL;
+
+-- -----------------------------------------------------
+-- Make sure that constraints on the 7.0 schema tables
+-- have appropriate referential actions. All tables
+-- which join the configuration elements with the
+-- servers should perform cascade deletion.
+-- -----------------------------------------------------
+
+ALTER TABLE dhcp4_global_parameter_server
+ DROP FOREIGN KEY fk_dhcp4_global_parameter_server_server_id;
+
+ALTER TABLE dhcp4_global_parameter_server
+ ADD CONSTRAINT fk_dhcp4_global_parameter_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp4_option_def_server
+ DROP FOREIGN KEY fk_dhcp4_option_def_server_server_id;
+
+ALTER TABLE dhcp4_option_def_server
+ ADD CONSTRAINT fk_dhcp4_option_def_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp4_shared_network_server
+ DROP FOREIGN KEY fk_dhcp4_shared_network_server_server_id;
+
+ALTER TABLE dhcp4_shared_network_server
+ ADD CONSTRAINT fk_dhcp4_shared_network_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp4_subnet_server
+ DROP FOREIGN KEY fk_dhcp4_subnet_server_server_id;
+
+ALTER TABLE dhcp4_subnet_server
+ ADD CONSTRAINT fk_dhcp4_subnet_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp4_options_server
+ DROP FOREIGN KEY fk_dhcp4_options_server_server_id;
+
+ALTER TABLE dhcp4_options_server
+ ADD CONSTRAINT fk_dhcp4_options_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp6_global_parameter_server
+ DROP FOREIGN KEY fk_dhcp6_global_parameter_server_server_id;
+
+ALTER TABLE dhcp6_global_parameter_server
+ ADD CONSTRAINT fk_dhcp6_global_parameter_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp6_option_def_server
+ DROP FOREIGN KEY fk_dhcp6_option_def_server_server_id;
+
+ALTER TABLE dhcp6_option_def_server
+ ADD CONSTRAINT fk_dhcp6_option_def_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp6_shared_network_server
+ DROP FOREIGN KEY fk_dhcp6_shared_network_server_server_id;
+
+ALTER TABLE dhcp6_shared_network_server
+ ADD CONSTRAINT fk_dhcp6_shared_network_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp6_subnet_server
+ DROP FOREIGN KEY fk_dhcp6_subnet_server_server_id;
+
+ALTER TABLE dhcp6_subnet_server
+ ADD CONSTRAINT fk_dhcp6_subnet_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp6_options_server
+ DROP FOREIGN KEY fk_dhcp6_options_server_option_id;
+
+ALTER TABLE dhcp6_options_server
+ ADD CONSTRAINT fk_dhcp6_options_server_option_id
+ FOREIGN KEY (option_id)
+ REFERENCES dhcp6_options (option_id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+-- -----------------------------------------------------
+-- Table `dhcp4_audit_revision`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_audit_revision (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ modification_ts TIMESTAMP NOT NULL,
+ log_message TEXT,
+ server_id BIGINT(10) UNSIGNED,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_audit_revision_by_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Drop columns from the dhcp4_audit table which now
+-- belong to the dhcp4_audit_revision.
+-- -----------------------------------------------------
+ALTER TABLE dhcp4_audit
+ DROP COLUMN modification_ts,
+ DROP COLUMN log_message;
+
+-- -----------------------------------------------------
+-- Add column revision_id and the foreign key with a
+-- reference to the dhcp4_audit_revision table.
+-- -----------------------------------------------------
+ALTER TABLE dhcp4_audit
+ ADD COLUMN revision_id BIGINT(20) UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp4_audit
+ ADD CONSTRAINT fk_dhcp4_audit_revision FOREIGN KEY (revision_id)
+ REFERENCES dhcp4_audit_revision (id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp4_audit_revision table and sets appropriate session
+-- variables to be used while creating the audit entries
+-- by triggers. This procedure should be called at the
+-- beginning of a transaction which modifies configuration
+-- data in the database, e.g. when new subnet is added.
+--
+-- Parameters:
+-- - audit_ts timestamp to be associated with the audit
+-- revision.
+-- - server_tag is used to retrieve the server_id which
+-- associates the changes applied with the particular
+-- server or all servers.
+-- - audit_log_message is a log message associates with
+-- the audit revision.
+-- - cascade_transaction is assigned to a session
+-- variable which is used in some triggers to determine
+-- if the audit entry should be created for them or
+-- not. Specifically, this is used when DHCP options
+-- are inserted, updated or deleted. If such modification
+-- is a part of the larger change (e.g. change in the
+-- subnet the options belong to) the dedicated audit
+-- entry for options must not be created. On the other
+-- hand, if the global option is being added, the
+-- audit entry for the option must be created because
+-- it is the sole object modified in that case.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createAuditRevisionDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createAuditRevisionDHCP4(IN audit_ts TIMESTAMP,
+ IN server_tag VARCHAR(256),
+ IN audit_log_message TEXT,
+ IN cascade_transaction TINYINT(1))
+BEGIN
+ DECLARE srv_id BIGINT(20);
+ IF @disable_audit IS NULL OR @disable_audit = 0 THEN
+ SELECT id INTO srv_id FROM dhcp4_server WHERE tag = server_tag;
+ INSERT INTO dhcp4_audit_revision (modification_ts, server_id, log_message)
+ VALUES (audit_ts, srv_id, audit_log_message);
+ SET @audit_revision_id = LAST_INSERT_ID();
+ SET @cascade_transaction = cascade_transaction;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp4_audit table. It should be called from the
+-- triggers of the tables where the config modifications
+-- are applied. The @audit_revision_id variable contains
+-- the revision id to be placed in the audit entries.
+--
+-- The following parameters are passed to this procedure:
+-- - object_type_val: name of the table to be associated
+-- with the applied changes.
+-- - object_id_val: identifier of the modified object in
+-- that table.
+-- - modification_type_val: string value indicating the
+-- type of the change, i.e. "create", "update" or
+-- "delete".
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- ----------------------------------------------------
+DROP PROCEDURE IF EXISTS createAuditEntryDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createAuditEntryDHCP4(IN object_type_val VARCHAR(256),
+ IN object_id_val BIGINT(20) UNSIGNED,
+ IN modification_type_val VARCHAR(32))
+BEGIN
+ IF @disable_audit IS NULL OR @disable_audit = 0 THEN
+ INSERT INTO dhcp4_audit (object_type, object_id, modification_type, revision_id)
+ VALUES (object_type_val, object_id_val, \
+ (SELECT id FROM modification WHERE modification_type = modification_type_val), \
+ @audit_revision_id);
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Triggers used to create entries in the audit
+-- tables upon insertion, update or deletion of the
+-- configuration entries.
+-- -----------------------------------------------------
+
+# Create dhcp4_global_parameter insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_global_parameter_AINS AFTER INSERT ON dhcp4_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_global_parameter', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_global_parameter update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_global_parameter_AUPD AFTER UPDATE ON dhcp4_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_global_parameter', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_global_parameter delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_global_parameter_ADEL AFTER DELETE ON dhcp4_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_global_parameter', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_subnet insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_subnet_AINS AFTER INSERT ON dhcp4_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_subnet', NEW.subnet_id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_subnet update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_subnet_AUPD AFTER UPDATE ON dhcp4_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_subnet', NEW.subnet_id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_subnet delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_subnet_ADEL AFTER DELETE ON dhcp4_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_subnet', OLD.subnet_id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_shared_network insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_shared_network_AINS AFTER INSERT ON dhcp4_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_shared_network update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_shared_network_AUPD AFTER UPDATE ON dhcp4_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_shared_network delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_shared_network_ADEL AFTER DELETE ON dhcp4_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_option_def insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_option_def_AINS AFTER INSERT ON dhcp4_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_option_def', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_option_def update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_option_def_AUPD AFTER UPDATE ON dhcp4_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_option_def', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_option_def delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_option_def_ADEL AFTER DELETE ON dhcp4_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_option_def', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates an audit entry for a
+-- DHCPv4 option. Depending on the scope of the option
+-- the audit entry can be created for various levels
+-- of configuration hierarchy. If this is a global
+-- option the audit entry is created for this option
+-- for CREATE, UPDATE or DELETE. If the option is being
+-- added for an owning option, e.g. for a subnet, the
+-- audit entry is created as an UPDATE to this object.
+-- From the Kea perspective such option addition will
+-- be seen as a subnet update and the server will fetch
+-- the whole subnet and merge it into its configuration.
+-- The audit entry is not created if it was already
+-- created as part of the current transaction.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createOptionAuditDHCP4(IN modification_type VARCHAR(32),
+ IN scope_id TINYINT(3) UNSIGNED,
+ IN option_id BIGINT(20) UNSIGNED,
+ IN subnet_id INT(10) UNSIGNED,
+ IN host_id INT(10) UNSIGNED,
+ IN network_name VARCHAR(128),
+ IN pool_id BIGINT(20))
+BEGIN
+ # These variables will hold shared network id and subnet id that
+ # we will select.
+ DECLARE snid VARCHAR(128);
+ DECLARE sid INT(10) UNSIGNED;
+
+ # Cascade transaction flag is set to 1 to prevent creation of
+ # the audit entries for the options when the options are
+ # created as part of the parent object creation or update.
+ # For example: when the option is added as part of the subnet
+ # addition, the cascade transaction flag is equal to 1. If
+ # the option is added into the existing subnet the cascade
+ # transaction is equal to 0. Note that depending on the option
+ # scope the audit entry will contain the object_type value
+ # of the parent object to cause the server to replace the
+ # entire subnet. The only case when the object_type will be
+ # set to 'dhcp4_options' is when a global option is added.
+ # Global options do not have the owner.
+ IF @cascade_transaction IS NULL OR @cascade_transaction = 0 THEN
+ # todo: host manager hasn't been updated to use audit
+ # mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ # If a global option is added or modified, create audit
+ # entry for the 'dhcp4_options' table.
+ CALL createAuditEntryDHCP4('dhcp4_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ # If subnet specific option is added or modified, create
+ # audit entry for the entire subnet, which indicates that
+ # it should be treated as the subnet update.
+ CALL createAuditEntryDHCP4('dhcp4_subnet', subnet_id, "update");
+ ELSEIF scope_id = 4 THEN
+ # If shared network specific option is added or modified,
+ # create audit entry for the shared network which
+ # indicates that it should be treated as the shared
+ # network update.
+ SELECT id INTO snid FROM dhcp4_shared_network WHERE name = network_name LIMIT 1;
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', snid, "update");
+ ELSEIF scope_id = 5 THEN
+ # If pool specific option is added or modified, create
+ # audit entry for the subnet which this pool belongs to.
+ SELECT dhcp4_pool.subnet_id INTO sid FROM dhcp4_pool WHERE id = pool_id;
+ CALL createAuditEntryDHCP4('dhcp4_subnet', sid, "update");
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+# Create dhcp4_options insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_AINS AFTER INSERT ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("create", NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id);
+ END $$
+DELIMITER ;
+
+# Create dhcp4_options update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_AUPD AFTER UPDATE ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("update", NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id);
+ END $$
+DELIMITER ;
+
+# Create dhcp4_options delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_ADEL AFTER DELETE ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("delete", OLD.scope_id, OLD.option_id, OLD.dhcp4_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id);
+ END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Table `parameter_data_type`
+-- Reflects an enum used by Kea to define supported
+-- data types for the simple configuration parameters,
+-- e.g. global parameters used by DHCP servers.
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS parameter_data_type (
+ id TINYINT UNSIGNED NOT NULL PRIMARY KEY,
+ name VARCHAR(32) NOT NULL
+) ENGINE = InnoDB;
+
+START TRANSACTION;
+INSERT INTO parameter_data_type VALUES (0, 'integer');
+INSERT INTO parameter_data_type VALUES (1, 'real');
+INSERT INTO parameter_data_type VALUES (2, 'boolean');
+INSERT INTO parameter_data_type VALUES (4, 'string');
+COMMIT;
+
+ALTER TABLE dhcp4_global_parameter
+ ADD COLUMN parameter_type TINYINT UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp4_global_parameter
+ ADD CONSTRAINT fk_dhcp4_global_parameter_type FOREIGN KEY (parameter_type)
+ REFERENCES parameter_data_type (id);
+
+ALTER TABLE dhcp6_global_parameter
+ ADD COLUMN parameter_type TINYINT UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp6_global_parameter
+ ADD CONSTRAINT fk_dhcp6_global_parameter_type FOREIGN KEY (parameter_type)
+ REFERENCES parameter_data_type (id);
+
+
+-- Rename dhcp6_subnet_id column of dhcp6_pool and dhcp6_pd_pool
+
+ALTER TABLE dhcp6_pool
+ DROP FOREIGN KEY fk_dhcp6_pool_subnet_id;
+DROP INDEX fk_dhcp6_pool_subnet_id
+ ON dhcp6_pool;
+
+ALTER TABLE dhcp6_pd_pool
+ DROP FOREIGN KEY fk_dhcp6_pd_pool_subnet_id;
+DROP INDEX fk_dhcp6_pd_pool_subnet_id
+ ON dhcp6_pd_pool;
+
+ALTER TABLE dhcp6_pool
+ CHANGE dhcp6_subnet_id subnet_id INT(10) UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp6_pd_pool
+ CHANGE dhcp6_subnet_id subnet_id INT(10) UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp6_pool
+ ADD CONSTRAINT fk_dhcp6_pool_subnet_id
+ FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_pd_pool
+ ADD CONSTRAINT fk_dhcp6_pd_pool_subnet_id
+ FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- align dhcp6_shared_network_server indexes on dhcp4_shared_network_server
+
+ALTER TABLE dhcp6_shared_network_server
+ ADD PRIMARY KEY (shared_network_id, server_id);
+
+ALTER TABLE dhcp6_shared_network_server
+ DROP FOREIGN KEY fk_dhcp6_shared_network_server_shared_network_id;
+DROP INDEX fk_dhcp6_shared_network_server_shared_network_id
+ ON dhcp6_shared_network_server;
+ALTER TABLE dhcp6_shared_network_server
+ ADD CONSTRAINT fk_dhcp6_shared_network_server_shared_network_id
+ FOREIGN KEY (shared_network_id)
+ REFERENCES dhcp6_shared_network (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+-- Update dhcp4_subnet_server and dhcp6_subnet_server to allow update
+-- on the prefix too by setting the CASCADE action.
+
+ALTER TABLE dhcp4_subnet_server
+ DROP FOREIGN KEY fk_dhcp4_subnet_server_subnet_id;
+ALTER TABLE dhcp4_subnet_server
+ ADD CONSTRAINT fk_dhcp4_subnet_server_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp4_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_subnet_server
+ DROP FOREIGN KEY fk_dhcp6_subnet_server_subnet_id;
+ALTER TABLE dhcp6_subnet_server
+ ADD CONSTRAINT fk_dhcp6_subnet_server_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- -----------------------------------------------------
+-- Table `dhcp6_audit_revision`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_audit_revision (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ modification_ts TIMESTAMP NOT NULL,
+ log_message TEXT,
+ server_id BIGINT(10) UNSIGNED,
+ PRIMARY KEY (id),
+ KEY key_dhcp6_audit_revision_by_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Drop columns from the dhcp6_audit table which now
+-- belong to the dhcp6_audit_revision.
+-- -----------------------------------------------------
+ALTER TABLE dhcp6_audit
+ DROP COLUMN modification_ts,
+ DROP COLUMN log_message;
+
+-- -----------------------------------------------------
+-- Add column revision_id and the foreign key with a
+-- reference to the dhcp6_audit_revision table.
+-- -----------------------------------------------------
+ALTER TABLE dhcp6_audit
+ ADD COLUMN revision_id BIGINT(20) UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp6_audit
+ ADD CONSTRAINT fk_dhcp6_audit_revision FOREIGN KEY (revision_id)
+ REFERENCES dhcp6_audit_revision (id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp6_audit_revision table and sets appropriate session
+-- variables to be used while creating the audit entries
+-- by triggers. This procedure should be called at the
+-- beginning of a transaction which modifies configuration
+-- data in the database, e.g. when new subnet is added.
+--
+-- Parameters:
+-- - audit_ts timestamp to be associated with the audit
+-- revision.
+-- - server_tag is used to retrieve the server_id which
+-- associates the changes applied with the particular
+-- server or all servers.
+-- - audit_log_message is a log message associates with
+-- the audit revision.
+-- - cascade_transaction is assigned to a session
+-- variable which is used in some triggers to determine
+-- if the audit entry should be created for them or
+-- not. Specifically, this is used when DHCP options
+-- are inserted, updated or deleted. If such modification
+-- is a part of the larger change (e.g. change in the
+-- subnet the options belong to) the dedicated audit
+-- entry for options must not be created. On the other
+-- hand, if the global option is being added, the
+-- audit entry for the option must be created because
+-- it is the sole object modified in that case.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createAuditRevisionDHCP6;
+DELIMITER $$
+CREATE PROCEDURE createAuditRevisionDHCP6(IN audit_ts TIMESTAMP,
+ IN server_tag VARCHAR(256),
+ IN audit_log_message TEXT,
+ IN cascade_transaction TINYINT(1))
+BEGIN
+ DECLARE srv_id BIGINT(20);
+ IF @disable_audit IS NULL OR @disable_audit = 0 THEN
+ SELECT id INTO srv_id FROM dhcp6_server WHERE tag = server_tag;
+ INSERT INTO dhcp6_audit_revision (modification_ts, server_id, log_message)
+ VALUES (audit_ts, srv_id, audit_log_message);
+ SET @audit_revision_id = LAST_INSERT_ID();
+ SET @cascade_transaction = cascade_transaction;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp6_audit table. It should be called from the
+-- triggers of the tables where the config modifications
+-- are applied. The @audit_revision_id variable contains
+-- the revision id to be placed in the audit entries.
+--
+-- The following parameters are passed to this procedure:
+-- - object_type_val: name of the table to be associated
+-- with the applied changes.
+-- - object_id_val: identifier of the modified object in
+-- that table.
+-- - modification_type_val: string value indicating the
+-- type of the change, i.e. "create", "update" or
+-- "delete".
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- ----------------------------------------------------
+DROP PROCEDURE IF EXISTS createAuditEntryDHCP6;
+DELIMITER $$
+CREATE PROCEDURE createAuditEntryDHCP6(IN object_type_val VARCHAR(256),
+ IN object_id_val BIGINT(20) UNSIGNED,
+ IN modification_type_val VARCHAR(32))
+BEGIN
+ IF @disable_audit IS NULL OR @disable_audit = 0 THEN
+ INSERT INTO dhcp6_audit (object_type, object_id, modification_type, revision_id)
+ VALUES (object_type_val, object_id_val, \
+ (SELECT id FROM modification WHERE modification_type = modification_type_val), \
+ @audit_revision_id);
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Triggers used to create entries in the audit
+-- tables upon insertion, update or deletion of the
+-- configuration entries.
+-- -----------------------------------------------------
+
+# Create dhcp6_global_parameter insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_global_parameter_AINS AFTER INSERT ON dhcp6_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_global_parameter', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_global_parameter update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_global_parameter_AUPD AFTER UPDATE ON dhcp6_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_global_parameter', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_global_parameter delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_global_parameter_ADEL AFTER DELETE ON dhcp6_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_global_parameter', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_subnet insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_subnet_AINS AFTER INSERT ON dhcp6_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_subnet', NEW.subnet_id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_subnet update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_subnet_AUPD AFTER UPDATE ON dhcp6_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_subnet', NEW.subnet_id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_subnet delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_subnet_ADEL AFTER DELETE ON dhcp6_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_subnet', OLD.subnet_id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_shared_network insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_shared_network_AINS AFTER INSERT ON dhcp6_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_shared_network update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_shared_network_AUPD AFTER UPDATE ON dhcp6_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_shared_network delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_shared_network_ADEL AFTER DELETE ON dhcp6_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_option_def insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_option_def_AINS AFTER INSERT ON dhcp6_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_option_def', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_option_def update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_option_def_AUPD AFTER UPDATE ON dhcp6_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_option_def', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_option_def delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_option_def_ADEL AFTER DELETE ON dhcp6_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_option_def', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates an audit entry for a
+-- DHCPv6 option. Depending on the scope of the option
+-- the audit entry can be created for various levels
+-- of configuration hierarchy. If this is a global
+-- option the audit entry is created for this option
+-- for CREATE, UPDATE or DELETE. If the option is being
+-- added for an owning option, e.g. for a subnet, the
+-- audit entry is created as an UPDATE to this object.
+-- From the Kea perspective such option addition will
+-- be seen as a subnet update and the server will fetch
+-- the whole subnet and merge it into its configuration.
+-- The audit entry is not created if it was already
+-- created as part of the current transaction.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP6;
+DELIMITER $$
+CREATE PROCEDURE createOptionAuditDHCP6(IN modification_type VARCHAR(32),
+ IN scope_id TINYINT(3) UNSIGNED,
+ IN option_id BIGINT(20) UNSIGNED,
+ IN subnet_id INT(10) UNSIGNED,
+ IN host_id INT(10) UNSIGNED,
+ IN network_name VARCHAR(128),
+ IN pool_id BIGINT(20),
+ IN pd_pool_id BIGINT(20))
+BEGIN
+ # These variables will hold shared network id and subnet id that
+ # we will select.
+ DECLARE snid VARCHAR(128);
+ DECLARE sid INT(10) UNSIGNED;
+
+ # Cascade transaction flag is set to 1 to prevent creation of
+ # the audit entries for the options when the options are
+ # created as part of the parent object creation or update.
+ # For example: when the option is added as part of the subnet
+ # addition, the cascade transaction flag is equal to 1. If
+ # the option is added into the existing subnet the cascade
+ # transaction is equal to 0. Note that depending on the option
+ # scope the audit entry will contain the object_type value
+ # of the parent object to cause the server to replace the
+ # entire subnet. The only case when the object_type will be
+ # set to 'dhcp6_options' is when a global option is added.
+ # Global options do not have the owner.
+ IF @cascade_transaction IS NULL OR @cascade_transaction = 0 THEN
+ # todo: host manager hasn't been updated to use audit
+ # mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ # If a global option is added or modified, create audit
+ # entry for the 'dhcp6_options' table.
+ CALL createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ # If subnet specific option is added or modified, create
+ # audit entry for the entire subnet, which indicates that
+ # it should be treated as the subnet update.
+ CALL createAuditEntryDHCP6('dhcp6_subnet', subnet_id, "update");
+ ELSEIF scope_id = 4 THEN
+ # If shared network specific option is added or modified,
+ # create audit entry for the shared network which
+ # indicates that it should be treated as the shared
+ # network update.
+ SELECT id INTO snid FROM dhcp6_shared_network WHERE name = network_name LIMIT 1;
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', snid, "update");
+ ELSEIF scope_id = 5 THEN
+ # If pool specific option is added or modified, create
+ # audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ CALL createAuditEntryDHCP6('dhcp6_subnet', sid, "update");
+ ELSEIF scope_id = 6 THEN
+ # If pd pool specific option is added or modified, create
+ # audit entry for the subnet which this pd pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pd_pool_id;
+ CALL createAuditEntryDHCP6('dhcp6_subnet', sid, "update");
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+# Create dhcp6_options insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_AINS AFTER INSERT ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("create", NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id, NEW.pd_pool_id);
+ END $$
+DELIMITER ;
+
+# Create dhcp6_options update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_AUPD AFTER UPDATE ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("update", NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id, NEW.pd_pool_id);
+ END $$
+DELIMITER ;
+
+# Create dhcp6_options delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_ADEL AFTER DELETE ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("delete", OLD.scope_id, OLD.option_id, OLD.dhcp6_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id, OLD.pd_pool_id);
+ END $$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '8', minor = '0';
+
+# This line concludes database upgrade to version 8.0.
+
+# Add lifetime bounds
+ALTER TABLE dhcp4_shared_network
+ ADD COLUMN min_valid_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_valid_lifetime INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp4_subnet
+ ADD COLUMN min_valid_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_valid_lifetime INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ ADD COLUMN min_preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN min_valid_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_valid_lifetime INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ ADD COLUMN min_preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN min_valid_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_valid_lifetime INT(10) DEFAULT NULL;
+
+# Create dhcp4_server insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_server_AINS AFTER INSERT ON dhcp4_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_server', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_server update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_server_AUPD AFTER UPDATE ON dhcp4_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_server', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_server delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_server_ADEL AFTER DELETE ON dhcp4_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_server', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_server insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_server_AINS AFTER INSERT ON dhcp6_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_server', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_server update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_server_AUPD AFTER UPDATE ON dhcp6_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_server', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_server delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_server_ADEL AFTER DELETE ON dhcp6_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_server', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Put the auth key in hexadecimal (double size but far more user friendly).
+ALTER TABLE hosts
+ MODIFY COLUMN auth_key VARCHAR(32) NULL;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '8', minor = '1';
+
+# This line concludes database upgrade to version 8.1.
+
+# Drop existing trigger on the dhcp4_shared_network table.
+DROP TRIGGER dhcp4_shared_network_ADEL;
+
+# Create new trigger which will delete options associated with the shared
+# network.
+DELIMITER $$
+CREATE TRIGGER dhcp4_shared_network_BDEL BEFORE DELETE ON dhcp4_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, "delete");
+ DELETE FROM dhcp4_options WHERE shared_network_name = OLD.name;
+ END $$
+DELIMITER ;
+
+# Drop existing trigger on the dhcp4_subnet table.
+DROP TRIGGER dhcp4_subnet_ADEL;
+
+# Create new trigger which will delete pools associated with the subnet and
+# the options associated with the subnet.
+DELIMITER $$
+CREATE TRIGGER dhcp4_subnet_BDEL BEFORE DELETE ON dhcp4_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_subnet', OLD.subnet_id, "delete");
+ DELETE FROM dhcp4_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp4_options WHERE dhcp4_subnet_id = OLD.subnet_id;
+ END $$
+DELIMITER ;
+
+# Do not perform cascade deletion of the data in the dhcp4_pool because
+# the cascade deletion does not execute triggers associated with the table.
+# Instead we are going to use triggers on the dhcp4_subnet table.
+ALTER TABLE dhcp4_pool
+ DROP FOREIGN KEY fk_dhcp4_pool_subnet_id;
+
+ALTER TABLE dhcp4_pool
+ ADD CONSTRAINT fk_dhcp4_pool_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp4_subnet (subnet_id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+# Drop existing trigger on the dhcp6_shared_network table.
+DROP TRIGGER dhcp6_shared_network_ADEL;
+
+# Create new trigger which will delete options associated with the shared
+# network.
+DELIMITER $$
+CREATE TRIGGER dhcp6_shared_network_BDEL BEFORE DELETE ON dhcp6_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, "delete");
+ DELETE FROM dhcp6_options WHERE shared_network_name = OLD.name;
+ END $$
+DELIMITER ;
+
+# Drop existing trigger on the dhcp6_subnet table.
+DROP TRIGGER dhcp6_subnet_ADEL;
+
+# Create new trigger which will delete pools associated with the subnet and
+# the options associated with the subnet.
+DELIMITER $$
+CREATE TRIGGER dhcp6_subnet_BDEL BEFORE DELETE ON dhcp6_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_subnet', OLD.subnet_id, "delete");
+ DELETE FROM dhcp6_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp6_pd_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp6_options WHERE dhcp6_subnet_id = OLD.subnet_id;
+ END $$
+DELIMITER ;
+
+# Do not perform cascade deletion of the data in the dhcp6_pool and dhcp6_pd_pool
+# because the cascaded deletion does not execute triggers associated with the table.
+# Instead we are going to use triggers on the dhcp6_subnet table.
+ALTER TABLE dhcp6_pool
+ DROP FOREIGN KEY fk_dhcp6_pool_subnet_id;
+
+ALTER TABLE dhcp6_pd_pool
+ DROP FOREIGN KEY fk_dhcp6_pd_pool_subnet_id;
+
+ALTER TABLE dhcp6_pool
+ ADD CONSTRAINT fk_dhcp6_pool_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_pd_pool
+ ADD CONSTRAINT fk_dhcp6_pd_pool_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+# Create trigger which removes pool specific options upon removal of
+# the pool.
+DELIMITER $$
+CREATE TRIGGER dhcp6_pd_pool_BDEL BEFORE DELETE ON dhcp6_pd_pool FOR EACH ROW
+BEGIN
+DELETE FROM dhcp6_options WHERE scope_id = 6 AND pd_pool_id = OLD.id;
+END
+$$
+DELIMITER ;
+
+# Add missing columns in pools.
+ALTER TABLE dhcp4_pool
+ ADD COLUMN client_class VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN require_client_classes LONGTEXT,
+ ADD COLUMN user_context LONGTEXT;
+
+ALTER TABLE dhcp6_pd_pool
+ ADD COLUMN excluded_prefix VARCHAR(45) DEFAULT NULL,
+ ADD COLUMN excluded_prefix_length TINYINT(3) NOT NULL,
+ ADD COLUMN client_class VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN require_client_classes LONGTEXT,
+ ADD COLUMN user_context LONGTEXT;
+
+ALTER TABLE dhcp6_pool
+ ADD COLUMN client_class VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN require_client_classes LONGTEXT,
+ ADD COLUMN user_context LONGTEXT;
+
+-- -----------------------------------------------------
+--
+-- New version of the createOptionAuditDHCP4 stored
+-- procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - modification_ts: modification timestamp of the
+-- option.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createOptionAuditDHCP4(IN modification_type VARCHAR(32),
+ IN scope_id TINYINT(3) UNSIGNED,
+ IN option_id BIGINT(20) UNSIGNED,
+ IN subnet_id INT(10) UNSIGNED,
+ IN host_id INT(10) UNSIGNED,
+ IN network_name VARCHAR(128),
+ IN pool_id BIGINT(20),
+ IN modification_ts TIMESTAMP)
+BEGIN
+ # These variables will hold shared network id and subnet id that
+ # we will select.
+ DECLARE snid VARCHAR(128);
+ DECLARE sid INT(10) UNSIGNED;
+
+ # Cascade transaction flag is set to 1 to prevent creation of
+ # the audit entries for the options when the options are
+ # created as part of the parent object creation or update.
+ # For example: when the option is added as part of the subnet
+ # addition, the cascade transaction flag is equal to 1. If
+ # the option is added into the existing subnet the cascade
+ # transaction is equal to 0. Note that depending on the option
+ # scope the audit entry will contain the object_type value
+ # of the parent object to cause the server to replace the
+ # entire subnet. The only case when the object_type will be
+ # set to 'dhcp4_options' is when a global option is added.
+ # Global options do not have the owner.
+ IF @cascade_transaction IS NULL OR @cascade_transaction = 0 THEN
+ # todo: host manager hasn't been updated to use audit
+ # mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ # If a global option is added or modified, create audit
+ # entry for the 'dhcp4_options' table.
+ CALL createAuditEntryDHCP4('dhcp4_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ # If subnet specific option is added or modified, update
+ # the modification timestamp of this subnet to allow the
+ # servers to refresh the subnet information. This will
+ # also result in creating an audit entry for this subnet.
+ UPDATE dhcp4_subnet AS s SET s.modification_ts = modification_ts
+ WHERE s.subnet_id = subnet_id;
+ ELSEIF scope_id = 4 THEN
+ # If shared network specific option is added or modified,
+ # update the modification timestamp of this shared network
+ # to allow the servers to refresh the shared network
+ # information. This will also result in creating an
+ # audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp4_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp4_shared_network AS n SET n.modification_ts = modification_ts
+ WHERE n.id = snid;
+ ELSEIF scope_id = 5 THEN
+ # If pool specific option is added or modified, update
+ # the modification timestamp of the owning subnet.
+ SELECT dhcp4_pool.subnet_id INTO sid FROM dhcp4_pool WHERE id = pool_id;
+ UPDATE dhcp4_subnet AS s SET s.modification_ts = modification_ts
+ WHERE s.subnet_id = sid;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+# Recreate dhcp4_options_AINS trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP4.
+DROP TRIGGER IF EXISTS dhcp4_options_AINS;
+
+# This trigger is executed after inserting a DHCPv4 option into the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_AINS AFTER INSERT ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("create", NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.modification_ts);
+ END $$
+DELIMITER ;
+
+# Recreate dhcp4_options_AUPD trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP4.
+DROP TRIGGER IF EXISTS dhcp4_options_AUPD;
+
+# This trigger is executed after updating a DHCPv4 option in the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_AUPD AFTER UPDATE ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("update", NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.modification_ts);
+ END $$
+DELIMITER ;
+
+# Recreate dhcp4_options_ADEL trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP4.
+DROP TRIGGER IF EXISTS dhcp4_options_ADEL;
+
+# This trigger is executed after deleting a DHCPv4 option in the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_ADEL AFTER DELETE ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("delete", OLD.scope_id, OLD.option_id, OLD.dhcp4_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id,
+ NOW());
+ END $$
+DELIMITER ;
+
+
+-- -----------------------------------------------------
+--
+-- New version of the createOptionAuditDHCP4 stored
+-- procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- - modification_ts: modification timestamp of the
+-- option.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP6;
+DELIMITER $$
+CREATE PROCEDURE createOptionAuditDHCP6(IN modification_type VARCHAR(32),
+ IN scope_id TINYINT(3) UNSIGNED,
+ IN option_id BIGINT(20) UNSIGNED,
+ IN subnet_id INT(10) UNSIGNED,
+ IN host_id INT(10) UNSIGNED,
+ IN network_name VARCHAR(128),
+ IN pool_id BIGINT(20),
+ IN pd_pool_id BIGINT(20),
+ IN modification_ts TIMESTAMP)
+BEGIN
+ # These variables will hold shared network id and subnet id that
+ # we will select.
+ DECLARE snid VARCHAR(128);
+ DECLARE sid INT(10) UNSIGNED;
+
+ # Cascade transaction flag is set to 1 to prevent creation of
+ # the audit entries for the options when the options are
+ # created as part of the parent object creation or update.
+ # For example: when the option is added as part of the subnet
+ # addition, the cascade transaction flag is equal to 1. If
+ # the option is added into the existing subnet the cascade
+ # transaction is equal to 0. Note that depending on the option
+ # scope the audit entry will contain the object_type value
+ # of the parent object to cause the server to replace the
+ # entire subnet. The only case when the object_type will be
+ # set to 'dhcp6_options' is when a global option is added.
+ # Global options do not have the owner.
+ IF @cascade_transaction IS NULL OR @cascade_transaction = 0 THEN
+ # todo: host manager hasn't been updated to use audit
+ # mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ # If a global option is added or modified, create audit
+ # entry for the 'dhcp6_options' table.
+ CALL createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ # If subnet specific option is added or modified, update
+ # the modification timestamp of this subnet to allow the
+ # servers to refresh the subnet information. This will
+ # also result in creating an audit entry for this subnet.
+ UPDATE dhcp6_subnet AS s SET s.modification_ts = modification_ts
+ WHERE s.subnet_id = subnet_id;
+ ELSEIF scope_id = 4 THEN
+ # If shared network specific option is added or modified,
+ # update the modification timestamp of this shared network
+ # to allow the servers to refresh the shared network
+ # information. This will also result in creating an
+ # audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp6_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp6_shared_network AS n SET n.modification_ts = modification_ts
+ WHERE n.id = snid;
+ ELSEIF scope_id = 5 THEN
+ # If pool specific option is added or modified, update
+ # the modification timestamp of the owning subnet.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet AS s SET s.modification_ts = modification_ts
+ WHERE s.subnet_id = sid;
+ ELSEIF scope_id = 6 THEN
+ # If pd pool specific option is added or modified, create
+ # audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pd_pool_id;
+ UPDATE dhcp6_subnet AS s SET s.modification_ts = modification_ts
+ WHERE s.subnet_id = sid;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+# Recreate dhcp6_options_AINS trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP6.
+DROP TRIGGER IF EXISTS dhcp6_options_AINS;
+
+# This trigger is executed after inserting a DHCPv6 option into the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_AINS AFTER INSERT ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("create", NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.pd_pool_id, NEW.modification_ts);
+ END $$
+DELIMITER ;
+
+# Recreate dhcp6_options_AUPD trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP6.
+DROP TRIGGER IF EXISTS dhcp6_options_AUPD;
+
+# This trigger is executed after updating a DHCPv6 option in the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_AUPD AFTER UPDATE ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("update", NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.pd_pool_id, NEW.modification_ts);
+ END $$
+DELIMITER ;
+
+# Recreate dhcp6_options_ADEL trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP6.
+DROP TRIGGER IF EXISTS dhcp6_options_ADEL;
+
+# This trigger is executed after deleting a DHCPv6 option in the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_ADEL AFTER DELETE ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("delete", OLD.scope_id, OLD.option_id, OLD.dhcp6_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id,
+ OLD.pd_pool_id, NOW());
+ END $$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '8', minor = '2';
+
+# This line concludes database upgrade to version 8.2.
+
+# Create hostname index for host reservations
+CREATE INDEX hosts_by_hostname ON hosts (hostname);
+
+# Create hostname index for lease4
+CREATE INDEX lease4_by_hostname ON lease4 (hostname);
+
+# Create hostname index for lease6
+CREATE INDEX lease6_by_hostname ON lease6 (hostname);
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '0';
+
+# This line concludes database upgrade to version 9.0.
+
+# Add new DDNS related columns to shared networks and subnets
+ALTER TABLE dhcp4_shared_network
+ ADD COLUMN ddns_send_updates TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_no_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_client_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_replace_client_name TINYINT(3) DEFAULT NULL,
+ ADD COLUMN ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ADD COLUMN ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ ADD COLUMN ddns_send_updates TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_no_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_client_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_replace_client_name TINYINT(3) DEFAULT NULL,
+ ADD COLUMN ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ADD COLUMN ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL;
+
+ALTER TABLE dhcp4_subnet
+ ADD COLUMN ddns_send_updates TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_no_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_client_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_replace_client_name TINYINT(3) DEFAULT NULL,
+ ADD COLUMN ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ADD COLUMN ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ ADD COLUMN ddns_send_updates TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_no_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_client_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_replace_client_name TINYINT(3) DEFAULT NULL,
+ ADD COLUMN ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ADD COLUMN ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '1';
+
+# This line concludes database upgrade to version 9.1.
+
+# Add missing indexes (foreign keys) to the dhcp4_options table.
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_shared_network
+ FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network(name)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_subnet
+ FOREIGN KEY (dhcp4_subnet_id)
+ REFERENCES dhcp4_subnet(subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_pool
+ FOREIGN KEY (pool_id)
+ REFERENCES dhcp4_pool(id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+# Add missing indexes (foreign keys) to the dhcp6_options table.
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_shared_network
+ FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network(name)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_subnet
+ FOREIGN KEY (dhcp6_subnet_id)
+ REFERENCES dhcp6_subnet(subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_pool
+ FOREIGN KEY (pool_id)
+ REFERENCES dhcp6_pool(id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_pd_pool
+ FOREIGN KEY (pd_pool_id)
+ REFERENCES dhcp6_pd_pool(id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '2';
+
+# This line concludes database upgrade to version 9.2.
+
+# Fix stat_lease4_update trigger
+DROP TRIGGER stat_lease4_update;
+
+DELIMITER $$
+CREATE TRIGGER stat_lease4_update AFTER UPDATE ON lease4
+ FOR EACH ROW
+ BEGIN
+ IF OLD.subnet_id != NEW.subnet_id OR OLD.state != NEW.state THEN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the old state count if record exists
+ UPDATE lease4_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND state = OLD.state;
+ END IF;
+
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Increment the new state count if record exists
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ # Insert new state record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat VALUES (NEW.subnet_id, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Fix stat_lease4_delete trigger
+DROP TRIGGER stat_lease4_delete;
+
+DELIMITER $$
+CREATE TRIGGER stat_lease4_delete AFTER DELETE ON lease4
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the state count if record exists
+ UPDATE lease4_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND OLD.state = state;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Fix stat_lease6_update trigger
+DROP TRIGGER stat_lease6_update;
+
+DELIMITER $$
+CREATE TRIGGER stat_lease6_update AFTER UPDATE ON lease6
+ FOR EACH ROW
+ BEGIN
+ IF OLD.subnet_id != NEW.subnet_id OR
+ OLD.lease_type != NEW.lease_type OR
+ OLD.state != NEW.state THEN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the old state count if record exists
+ UPDATE lease6_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ # Insert new state record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat
+ VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Fix stat_lease6_delete trigger
+DROP TRIGGER stat_lease6_delete;
+
+DELIMITER $$
+CREATE TRIGGER stat_lease6_delete AFTER DELETE ON lease6
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the state count if record exists
+ UPDATE lease6_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '3';
+
+# This line concludes database upgrade to version 9.3.
+
+# Starting from this version we allow specifying multiple IP reservations
+# for the same address in certain DHCP configurations. The server may check
+# uniqueness of the IP addresses on its own. This is no longer checked at
+# the database level to facilitate the use cases when a single host may
+# get the same reserved IP address via different interfaces.
+
+# Replace the unique index with non-unique index so the queries for
+# hosts by IPv4 address are still efficient.
+DROP INDEX key_dhcp4_ipv4_address_subnet_id ON hosts;
+CREATE INDEX key_dhcp4_ipv4_address_subnet_id_identifier
+ ON hosts (ipv4_address ASC, dhcp4_subnet_id ASC);
+
+# Replace the unique index with non-unique index so the queries for
+# hosts by IPv6 address are still efficient.
+DROP INDEX key_dhcp6_address_prefix_len ON ipv6_reservations;
+CREATE INDEX key_dhcp6_address_prefix_len
+ ON ipv6_reservations (address ASC, prefix_len ASC);
+
+# Stop using a trigger to delete entries dependent on hosts table.
+# Use cascade action instead. This works better with complex delete
+# statements.
+DROP TRIGGER IF EXISTS host_BDEL;
+
+# Replace existing constraint to set cascade actions.
+ALTER TABLE ipv6_reservations DROP FOREIGN KEY fk_ipv6_reservations_Host;
+ALTER TABLE ipv6_reservations ADD CONSTRAINT fk_ipv6_reservations_Host
+ FOREIGN KEY (host_id)
+ REFERENCES hosts(host_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_Host
+ FOREIGN KEY (host_id)
+ REFERENCES hosts(host_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_Host
+ FOREIGN KEY (host_id)
+ REFERENCES hosts(host_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '4';
+
+# This line concludes database upgrade to version 9.4.
+
+# Add new reservations flags.
+ALTER TABLE dhcp4_subnet
+ ADD COLUMN reservations_global BOOL DEFAULT NULL,
+ ADD COLUMN reservations_in_subnet BOOL DEFAULT NULL,
+ ADD COLUMN reservations_out_of_pool BOOL DEFAULT NULL;
+
+ALTER TABLE dhcp4_shared_network
+ ADD COLUMN reservations_global BOOL DEFAULT NULL,
+ ADD COLUMN reservations_in_subnet BOOL DEFAULT NULL,
+ ADD COLUMN reservations_out_of_pool BOOL DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ ADD COLUMN reservations_global BOOL DEFAULT NULL,
+ ADD COLUMN reservations_in_subnet BOOL DEFAULT NULL,
+ ADD COLUMN reservations_out_of_pool BOOL DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ ADD COLUMN reservations_global BOOL DEFAULT NULL,
+ ADD COLUMN reservations_in_subnet BOOL DEFAULT NULL,
+ ADD COLUMN reservations_out_of_pool BOOL DEFAULT NULL;
+
+# DROP reservation_mode
+ALTER TABLE dhcp4_subnet DROP COLUMN reservation_mode;
+ALTER TABLE dhcp4_shared_network DROP COLUMN reservation_mode;
+ALTER TABLE dhcp6_subnet DROP COLUMN reservation_mode;
+ALTER TABLE dhcp6_shared_network DROP COLUMN reservation_mode;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '5';
+
+# This line concludes database upgrade to version 9.5.
+
+# Add new lease cache parameters.
+ALTER TABLE dhcp4_subnet
+ ADD COLUMN cache_threshold FLOAT DEFAULT NULL,
+ ADD COLUMN cache_max_age INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp4_shared_network
+ ADD COLUMN cache_threshold FLOAT DEFAULT NULL,
+ ADD COLUMN cache_max_age INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ ADD COLUMN cache_threshold FLOAT DEFAULT NULL,
+ ADD COLUMN cache_max_age INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ ADD COLUMN cache_threshold FLOAT DEFAULT NULL,
+ ADD COLUMN cache_max_age INT(10) DEFAULT NULL;
+
+# Add an auto-increment ID as primary key to support Percona.
+ALTER TABLE logs
+ ADD id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY;
+
+# Update the schema version number
+UPDATE schema_version
+ SET version = '9', minor = '6';
+
+# This line concludes database upgrade to version 9.6.
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv4 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp4_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class (
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ test TEXT,
+ next_server INT UNSIGNED DEFAULT NULL,
+ server_hostname VARCHAR(128) DEFAULT NULL,
+ boot_file_name VARCHAR(512) DEFAULT NULL,
+ only_if_required TINYINT NOT NULL DEFAULT '0',
+ valid_lifetime INT DEFAULT NULL,
+ min_valid_lifetime INT DEFAULT NULL,
+ max_valid_lifetime INT DEFAULT NULL,
+ depend_on_known_directly TINYINT NOT NULL DEFAULT '0',
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY id_UNIQUE (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp4_client_class_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp4_client_class table has a corresponding row
+-- in the dhcp4_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp4_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp4_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_indirectly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp4_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_order (
+ class_id BIGINT UNSIGNED NOT NULL,
+ order_index BIGINT UNSIGNED NOT NULL,
+ depend_on_known_indirectly TINYINT NOT NULL DEFAULT '0',
+ PRIMARY KEY (class_id),
+ KEY key_dhcp4_client_class_order_index (order_index),
+ CONSTRAINT fk_dhcp4_client_class_order_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp4_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_client_class_ADEL;
+DROP PROCEDURE IF EXISTS setClientClass4Order;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE setClientClass4Order(IN id BIGINT UNSIGNED,
+ IN follow_class_name VARCHAR(128),
+ IN old_follow_class_name VARCHAR(128))
+proc_label:BEGIN
+ -- This variable will be optionally set if the follow_class_name
+ -- column value is specified.
+ DECLARE follow_class_index BIGINT UNSIGNED;
+ DECLARE msg TEXT;
+
+ -- Remember currently used value of depend_on_known_indirectly.
+ SET @depend_on_known_indirectly = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order WHERE id = class_id
+ );
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ @depend_on_known_indirectly IS NOT NULL AND
+ ((follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ UPDATE dhcp4_client_class_order SET depend_on_known_indirectly = 0
+ WHERE class_id = id;
+ LEAVE proc_label;
+ END IF;
+
+ IF follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SET follow_class_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = follow_class_name
+ );
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with follow_class_name does
+ -- not exist.
+ SET msg = CONCAT('Class ', follow_class_name, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp4_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp4_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1
+ ORDER BY order_index DESC;
+ END IF;
+ ELSE
+ -- A caller did not specify the follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SET follow_class_index = (SELECT MAX(order_index) FROM dhcp4_client_class_order);
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ SET follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency AS d
+ INNER JOIN dhcp4_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ SET msg = CONCAT('Unable to move class with id ', id, ' because it would break its dependencies');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ REPLACE INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_AINS AFTER INSERT ON dhcp4_client_class FOR EACH ROW BEGIN
+ CALL setClientClass4Order(NEW.id, NEW.follow_class_name, NULL);
+ CALL createAuditEntryDHCP4('dhcp4_client_class', NEW.id, "create");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_AUPD AFTER UPDATE ON dhcp4_client_class FOR EACH ROW BEGIN
+ SET @depend_on_known_directly = OLD.depend_on_known_directly;
+ SET @client_class_id = NEW.id;
+ CALL setClientClass4Order(NEW.id, NEW.follow_class_name, OLD.follow_class_name);
+ CALL createAuditEntryDHCP4('dhcp4_client_class', NEW.id, "update");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to create dhcp4_client_class audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_ADEL AFTER DELETE ON dhcp4_client_class FOR EACH ROW BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_client_class', OLD.id, "delete");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp4_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp4_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_dependency (
+ class_id BIGINT UNSIGNED NOT NULL,
+ dependency_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (class_id,dependency_id),
+ KEY dhcp4_client_class_dependency_id_idx (dependency_id),
+ CONSTRAINT dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp4_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp4_client_class (id)
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_BINS;
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - class_id id client class,
+-- - dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv4ClientClassDependency(IN class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE class_index BIGINT UNSIGNED;
+ DECLARE dependency_index BIGINT UNSIGNED;
+ DECLARE err_msg TEXT;
+
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF class_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class id must not be NULL.';
+ END IF;
+ IF dependency_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency id must not be NULL.';
+ END IF;
+ -- Dependencies on self make no sense.
+ IF class_id = dependency_id THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class must not have dependency on self.';
+ END IF;
+ -- Check position of our class in the hierarchy.
+ SET class_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.id = class_id);
+ IF class_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- Check position of the dependency.
+ SET dependency_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = dependency_id
+ );
+ IF dependency_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', dependency_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' must not depend on class defined later with id ', dependency_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp4_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_server AS t1
+ LEFT JOIN dhcp4_client_class_server AS t2
+ ON t2.class_id = dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ SET err_msg = CONCAT('Unmet dependencies for client class with id ', class_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_check_dependency_BINS BEFORE INSERT ON dhcp4_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_AINS;
+DROP PROCEDURE IF EXISTS updateDHCPv4ClientClassKnownDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE updateDHCPv4ClientClassKnownDependency(IN client_class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE dependency TINYINT;
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SET dependency = (
+ SELECT depend_on_known_directly FROM dhcp4_client_class
+ WHERE id = dependency_id
+ );
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = 0 THEN
+ SET dependency = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order
+ WHERE class_id = dependency_id
+ );
+ END IF;
+ IF dependency <> 0 THEN
+ UPDATE dhcp4_client_class_order
+ SET depend_on_known_indirectly = 1
+ WHERE class_id = client_class_id;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_dependency_AINS AFTER INSERT ON dhcp4_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL updateDHCPv4ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassKnownDependencyChange;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv4 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv4ClientClassKnownDependencyChange()
+BEGIN
+ DECLARE depended TINYINT DEFAULT 0;
+ DECLARE depends TINYINT DEFAULT 0;
+
+ -- Session variables are set upon a client class update.
+ IF @client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency
+ WHERE dependency_id = @client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ IF @depend_on_known_directly <> 0 OR @depend_on_known_indirectly <> 0 THEN
+ SET depended = 1;
+ END IF;
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SET depends = (
+ SELECT depend_on_known_directly FROM dhcp4_client_class
+ WHERE id = @client_class_id
+ );
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = 0 THEN
+ SET depends = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order
+ WHERE class_id = @client_class_id
+ );
+ END IF;
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.';
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv4 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_server (
+ class_id bigint unsigned NOT NULL,
+ server_id bigint unsigned NOT NULL,
+ modification_ts timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ KEY fk_dhcp4_client_class_server_id (server_id),
+ CONSTRAINT fk_dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp4_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv4 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp4_option_def
+ ADD COLUMN class_id BIGINT UNSIGNED NULL DEFAULT NULL;
+
+ALTER TABLE dhcp4_option_def
+ ADD CONSTRAINT fk_dhcp4_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv6 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp6_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class (
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ test TEXT,
+ only_if_required TINYINT NOT NULL DEFAULT '0',
+ valid_lifetime INT DEFAULT NULL,
+ min_valid_lifetime INT DEFAULT NULL,
+ max_valid_lifetime INT DEFAULT NULL,
+ depend_on_known_directly TINYINT NOT NULL DEFAULT '0',
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY id_UNIQUE (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp6_client_class_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp6_client_class table has a corresponding row
+-- in the dhcp6_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp6_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp6_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_indirectly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp6_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_order (
+ class_id BIGINT UNSIGNED NOT NULL,
+ order_index BIGINT UNSIGNED NOT NULL,
+ depend_on_known_indirectly TINYINT NOT NULL DEFAULT '0',
+ PRIMARY KEY (class_id),
+ KEY key_dhcp6_client_class_order_index (order_index),
+ CONSTRAINT fk_dhcp6_client_class_order_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp6_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_client_class_ADEL;
+DROP PROCEDURE IF EXISTS setClientClass6Order;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name name of the previous class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE setClientClass6Order(IN id BIGINT UNSIGNED,
+ IN follow_class_name VARCHAR(128),
+ IN old_follow_class_name VARCHAR(128))
+proc_label:BEGIN
+ -- This variable will be optionally set if the follow_class_name
+ -- column value is specified.
+ DECLARE follow_class_index BIGINT UNSIGNED;
+ DECLARE msg TEXT;
+
+ -- Remember currently used value of depend_on_known_indirectly.
+ SET @depend_on_known_indirectly = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order WHERE id = class_id
+ );
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ @depend_on_known_indirectly IS NOT NULL AND
+ ((follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ UPDATE dhcp6_client_class_order SET depend_on_known_indirectly = 0
+ WHERE class_id = id;
+ LEAVE proc_label;
+ END IF;
+
+ IF follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SET follow_class_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = follow_class_name
+ );
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with follow_class_name does
+ -- not exist.
+ SET msg = CONCAT('Class ', follow_class_name, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp6_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp6_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1
+ ORDER BY order_index DESC;
+ END IF;
+ ELSE
+ -- A caller did not specify the follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SET follow_class_index = (SELECT MAX(order_index) FROM dhcp6_client_class_order);
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ SET follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency AS d
+ INNER JOIN dhcp6_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ SET msg = CONCAT('Unable to move class with id ', id, ' because it would break its dependencies');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ REPLACE INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_AINS AFTER INSERT ON dhcp6_client_class FOR EACH ROW BEGIN
+ CALL setClientClass6Order(NEW.id, NEW.follow_class_name, NULL);
+ CALL createAuditEntryDHCP6('dhcp6_client_class', NEW.id, "create");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_AUPD AFTER UPDATE ON dhcp6_client_class FOR EACH ROW BEGIN
+ SET @depend_on_known_directly = OLD.depend_on_known_directly;
+ SET @client_class_id = NEW.id;
+ CALL setClientClass6Order(NEW.id, NEW.follow_class_name, OLD.follow_class_name);
+ CALL createAuditEntryDHCP6('dhcp6_client_class', NEW.id, "update");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to create dhcp6_client_class audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_ADEL AFTER DELETE ON dhcp6_client_class FOR EACH ROW BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_client_class', OLD.id, "delete");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp6_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp6_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_dependency (
+ class_id BIGINT UNSIGNED NOT NULL,
+ dependency_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (class_id,dependency_id),
+ KEY dhcp6_client_class_dependency_id_idx (dependency_id),
+ CONSTRAINT dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp6_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp6_client_class (id)
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_BINS;
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - class_id id client class,
+-- - dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv6ClientClassDependency(IN class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE class_index BIGINT UNSIGNED;
+ DECLARE dependency_index BIGINT UNSIGNED;
+ DECLARE err_msg TEXT;
+
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF class_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class id must not be NULL.';
+ END IF;
+ IF dependency_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency id must not be NULL.';
+ END IF;
+ -- Dependencies on self make no sense.
+ IF class_id = dependency_id THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class must not have dependency on self.';
+ END IF;
+ -- Check position of our class in the hierarchy.
+ SET class_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.id = class_id);
+ IF class_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- Check position of the dependency.
+ SET dependency_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = dependency_id
+ );
+ IF dependency_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', dependency_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' must not depend on class defined later with id ', dependency_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp6_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_server AS t1
+ LEFT JOIN dhcp6_client_class_server AS t2
+ ON t2.class_id = dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ SET err_msg = CONCAT('Unmet dependencies for client class with id ', class_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_check_dependency_BINS BEFORE INSERT ON dhcp6_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_AINS;
+DROP PROCEDURE IF EXISTS updateDHCPv6ClientClassKnownDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE updateDHCPv6ClientClassKnownDependency(IN client_class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE dependency TINYINT;
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SET dependency = (
+ SELECT depend_on_known_directly FROM dhcp6_client_class
+ WHERE id = dependency_id
+ );
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = 0 THEN
+ SET dependency = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order
+ WHERE class_id = dependency_id
+ );
+ END IF;
+ IF dependency <> 0 THEN
+ UPDATE dhcp6_client_class_order
+ SET depend_on_known_indirectly = 1
+ WHERE class_id = client_class_id;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_dependency_AINS AFTER INSERT ON dhcp6_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL updateDHCPv6ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassKnownDependencyChange;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv6 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv6ClientClassKnownDependencyChange()
+BEGIN
+ DECLARE depended TINYINT DEFAULT 0;
+ DECLARE depends TINYINT DEFAULT 0;
+
+ -- Session variables are set upon a client class update.
+ IF @client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency
+ WHERE dependency_id = @client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ IF @depend_on_known_directly <> 0 OR @depend_on_known_indirectly <> 0 THEN
+ SET depended = 1;
+ END IF;
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SET depends = (
+ SELECT depend_on_known_directly FROM dhcp6_client_class
+ WHERE id = @client_class_id
+ );
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = 0 THEN
+ SET depends = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order
+ WHERE class_id = @client_class_id
+ );
+ END IF;
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.';
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv6 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_server (
+ class_id bigint unsigned NOT NULL,
+ server_id bigint unsigned NOT NULL,
+ modification_ts timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ KEY fk_dhcp6_client_class_server_id (server_id),
+ CONSTRAINT fk_dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp6_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv6 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp6_option_def
+ ADD COLUMN class_id BIGINT UNSIGNED NULL DEFAULT NULL;
+
+ALTER TABLE dhcp6_option_def
+ ADD CONSTRAINT fk_dhcp6_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+# Update the schema version number
+UPDATE schema_version
+ SET version = '10', minor = '0';
+
+# This line concludes database upgrade to version 10.
+
+# Reverse index order to improve reclamation query performance
+# Add a constraint that any state value added to the lease4 must
+# map to a value in the lease_state table. Note we have to drop
+# the foreign keys first, then add them back.
+ALTER TABLE lease4 DROP FOREIGN KEY fk_lease4_state;
+DROP INDEX lease4_by_state_expire ON lease4;
+CREATE INDEX lease4_by_expire_state ON lease4 (expire ASC, state ASC);
+ALTER TABLE lease4
+ ADD CONSTRAINT fk_lease4_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+ALTER TABLE lease6 DROP FOREIGN KEY fk_lease6_state;
+CREATE INDEX lease6_by_expire_state ON lease6 (expire ASC, state ASC);
+DROP INDEX lease6_by_state_expire ON lease6;
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+# Added preferred lifetime columns v6 client classes.
+ALTER TABLE dhcp6_client_class
+ ADD COLUMN preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN min_preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_preferred_lifetime INT(10) DEFAULT NULL;
+
+# Update the schema version number
+UPDATE schema_version
+ SET version = '11', minor = '0';
+
+# This line concludes database upgrade to version 11.
+
+# Ensure that dhcp_client_class is NULL when an option does not
+# belong to any client class. Later, we will add foreign keys for
+# the dhcp_client_class columns, and it requires that the columns
+# are NULL when there are no corresponding client classes.
+SET @disable_audit = 1;
+UPDATE dhcp4_options SET dhcp_client_class = NULL
+ WHERE scope_id <> 2;
+UPDATE dhcp6_options SET dhcp_client_class = NULL
+ WHERE scope_id <> 2;
+SET @disable_audit = 0;
+
+# Add a foreign keys referencing a client classes. If an option is
+# associated with a client class, the option will be deleted
+# along with the deleted client class.
+ALTER TABLE dhcp4_options
+ ADD CONSTRAINT fk_dhcp4_options_client_class
+ FOREIGN KEY (dhcp_client_class)
+ REFERENCES dhcp4_client_class (name)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options
+ ADD CONSTRAINT fk_dhcp6_options_client_class
+ FOREIGN KEY (dhcp_client_class)
+ REFERENCES dhcp6_client_class (name)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+# Update the schema version number
+UPDATE schema_version
+ SET version = '12', minor = '0';
+
+-- This line concludes the schema upgrade to version 12.
+
+-- This line starts the schema upgrade to version 13.
+
+-- Create a function that separates a contiguous hexadecimal string
+-- into groups of two hexadecimals separated by colons.
+DROP FUNCTION IF EXISTS colonSeparatedHex;
+DELIMITER $$
+CREATE FUNCTION colonSeparatedHex(hex VARCHAR(64))
+RETURNS VARCHAR(64)
+DETERMINISTIC
+BEGIN
+ -- Declarations
+ DECLARE i INT;
+ DECLARE length INT;
+ DECLARE output VARCHAR(64);
+
+ -- Initializations
+ SET i = 3;
+ SET length = LENGTH(hex);
+
+ -- Add a leading zero if the first octet has a single hexadecimal character.
+ IF MOD(length, 2) = 1 THEN
+ SET hex = CONCAT('0', hex);
+ SET length = length + 1;
+ END IF;
+
+ -- Start with the first octet.
+ SET output = SUBSTR(hex, 1, 2);
+
+ -- Add one octet at a time and a leading colon with each.
+ label: WHILE i < length DO
+ SET output = CONCAT(output, ':', SUBSTR(hex, i, 2));
+ SET i = i + 2;
+ END WHILE label;
+
+ -- Memfile uses lowercase hexadecimals.
+ SET output = LOWER(output);
+
+ RETURN output;
+END $$
+DELIMITER ;
+
+-- Modify the procedure to output a memfile-ready CSV file.
+DROP PROCEDURE IF EXISTS lease4DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpData()
+BEGIN
+ SELECT
+ INET_NTOA(address),
+ IFNULL(colonSeparatedHex(HEX(hwaddr)), ''),
+ IFNULL(colonSeparatedHex(HEX(client_id)), ''),
+ valid_lifetime,
+ UNIX_TIMESTAMP(expire),
+ subnet_id,
+ fqdn_fwd,
+ fqdn_rev,
+ REPLACE(hostname, ',', '&#x2c'),
+ state,
+ REPLACE(IFNULL(user_context, ''), ',', '&#x2c')
+ FROM lease4
+ ORDER BY address;
+END $$
+DELIMITER ;
+
+-- hwtype and hwaddr_source need to be last to match memfile format.
+DROP PROCEDURE IF EXISTS lease6DumpHeader;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpHeader()
+BEGIN
+ SELECT 'address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state,user_context,hwtype,hwaddr_source';
+END $$
+DELIMITER ;
+
+-- Modify the procedure to output a memfile-ready CSV file.
+DROP PROCEDURE IF EXISTS lease6DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpData()
+BEGIN
+ SELECT
+ address,
+ IFNULL(colonSeparatedHex(HEX(duid)), ''),
+ valid_lifetime,
+ UNIX_TIMESTAMP(expire),
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd,
+ fqdn_rev,
+ REPLACE(hostname, ',', '&#x2c'),
+ IFNULL(colonSeparatedHex(HEX(hwaddr)), ''),
+ state,
+ REPLACE(IFNULL(user_context, ''), ',', '&#x2c'),
+ hwtype,
+ hwaddr_source
+ FROM lease6
+ ORDER BY address;
+END $$
+DELIMITER ;
+
+-- Create a procedure that inserts a v4 lease from memfile data.
+DELIMITER $$
+CREATE PROCEDURE lease4Upload(
+ IN address VARCHAR(15),
+ IN hwaddr VARCHAR(20),
+ IN client_id VARCHAR(128),
+ IN valid_lifetime INT UNSIGNED,
+ IN expire BIGINT UNSIGNED,
+ IN subnet_id INT UNSIGNED,
+ IN fqdn_fwd TINYINT,
+ IN fqdn_rev TINYINT,
+ IN hostname VARCHAR(255),
+ IN state INT UNSIGNED,
+ IN user_context TEXT
+)
+BEGIN
+ INSERT INTO lease4 (
+ address,
+ hwaddr,
+ client_id,
+ valid_lifetime,
+ expire,
+ subnet_id,
+ fqdn_fwd,
+ fqdn_rev,
+ hostname,
+ state,
+ user_context
+ ) VALUES (
+ INET_ATON(address),
+ UNHEX(REPLACE(hwaddr, ':', '')),
+ UNHEX(REPLACE(client_id, ':', '')),
+ valid_lifetime,
+ FROM_UNIXTIME(expire),
+ subnet_id,
+ fqdn_fwd,
+ fqdn_rev,
+ REPLACE(hostname, '&#x2c', ','),
+ state,
+ REPLACE(user_context, '&#x2c', ',')
+ );
+END $$
+DELIMITER ;
+
+-- Create a procedure that inserts a v6 lease from memfile data.
+DELIMITER $$
+CREATE PROCEDURE lease6Upload(
+ IN address VARCHAR(39),
+ IN duid VARCHAR(128),
+ IN valid_lifetime INT UNSIGNED,
+ IN expire BIGINT UNSIGNED,
+ IN subnet_id INT UNSIGNED,
+ IN pref_lifetime INT UNSIGNED,
+ IN lease_type TINYINT,
+ IN iaid INT UNSIGNED,
+ IN prefix_len TINYINT UNSIGNED,
+ IN fqdn_fwd TINYINT,
+ IN fqdn_rev TINYINT,
+ IN hostname VARCHAR(255),
+ IN hwaddr VARCHAR(64),
+ IN state INT UNSIGNED,
+ IN user_context TEXT,
+ IN hwtype SMALLINT,
+ IN hwaddr_source INT UNSIGNED
+)
+BEGIN
+ INSERT INTO lease6 (
+ address,
+ duid,
+ valid_lifetime,
+ expire,
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd,
+ fqdn_rev,
+ hostname,
+ hwaddr,
+ state,
+ user_context,
+ hwtype,
+ hwaddr_source
+ ) VALUES (
+ address,
+ UNHEX(REPLACE(duid, ':', '')),
+ valid_lifetime,
+ FROM_UNIXTIME(expire),
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd,
+ fqdn_rev,
+ REPLACE(hostname, '&#x2c', ','),
+ UNHEX(REPLACE(hwaddr, ':', '')),
+ state,
+ REPLACE(user_context, '&#x2c', ','),
+ hwtype,
+ hwaddr_source
+ );
+END $$
+DELIMITER ;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '13', minor = '0';
+
+-- This line concludes the schema upgrade to version 13.
+
+-- This line starts the schema upgrade to version 14.
+
+-- Modify shared-network-name foreign key constraint on dhcp4_subnet to not perform
+-- the update when the network is deleted the cascaded update will not execute
+-- dhcp4_subnet update trigger leaving the updated subnets without audit_entries.
+ALTER TABLE dhcp4_subnet
+ DROP FOREIGN KEY fk_dhcp4_subnet_shared_network;
+
+ALTER TABLE dhcp4_subnet
+ ADD CONSTRAINT fk_dhcp4_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network (name)
+ ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+-- Modify BEFORE delete trigger on dhcp4_shared_network to explicitly
+-- update dhcp4_subnets. This ensures there are audit entries for updated
+-- subnets.
+DROP TRIGGER dhcp4_shared_network_BDEL;
+
+DELIMITER $$
+CREATE TRIGGER dhcp4_shared_network_BDEL BEFORE DELETE ON dhcp4_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, "delete");
+ -- In MySQL Foreign key constraint triggered updates will not cascade, so we explicitly
+ -- update subnets first which should ensure they get audit entries.
+ UPDATE dhcp4_subnet SET shared_network_name = NULL WHERE shared_network_name = OLD.name;
+ DELETE FROM dhcp4_options WHERE shared_network_name = OLD.name;
+ END $$
+DELIMITER ;
+
+-- Modify shared-network-name foreign key constraint on dhcp6_subnet to not perform
+-- the update when the network is deleted the cascaded update will not execute
+-- dhcp6_subnet update trigger leaving the updated subnets without audit_entries.
+ALTER TABLE dhcp6_subnet
+ DROP FOREIGN KEY fk_dhcp6_subnet_shared_network;
+
+ALTER TABLE dhcp6_subnet
+ ADD CONSTRAINT fk_dhcp6_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network (name)
+ ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+-- Modify BEFORE delete trigger on dhcp6_shared_network to explicitly
+-- update dhcp6_subnets. This ensures there are audit entries for updated
+-- subnets.
+DROP TRIGGER dhcp6_shared_network_BDEL;
+
+DELIMITER $$
+CREATE TRIGGER dhcp6_shared_network_BDEL BEFORE DELETE ON dhcp6_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, "delete");
+ -- In MySQL Foreign key constraint triggered updates will not cascade, so we explicitly
+ -- update subnets first which should ensure they get audit entries.
+ UPDATE dhcp6_subnet SET shared_network_name = NULL WHERE shared_network_name = OLD.name;
+ DELETE FROM dhcp6_options WHERE shared_network_name = OLD.name;
+ END $$
+DELIMITER ;
+
+-- Add user_context column to client class tables.
+ALTER TABLE dhcp4_client_class ADD COLUMN user_context LONGTEXT NULL;
+ALTER TABLE dhcp6_client_class ADD COLUMN user_context LONGTEXT NULL;
+
+-- Schema changes related to lease limiting start here. --
+
+-- Recreate the triggers that update the leaseX_stat tables as stored procedures. --
+
+DROP PROCEDURE IF EXISTS lease4_AINS_lease4_stat;
+DELIMITER $$
+CREATE PROCEDURE lease4_AINS_lease4_stat(IN new_state TINYINT,
+ IN new_subnet_id INT UNSIGNED)
+BEGIN
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Update the state count if it exists.
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND state = new_state;
+
+ -- Insert the state count record if it does not exist.
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat VALUES (new_subnet_id, new_state, 1);
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease4_AUPD_lease4_stat;
+DELIMITER $$
+CREATE PROCEDURE lease4_AUPD_lease4_stat(IN old_state TINYINT,
+ IN old_subnet_id INT UNSIGNED,
+ IN new_state TINYINT,
+ IN new_subnet_id INT UNSIGNED)
+BEGIN
+ IF old_subnet_id != new_subnet_id OR old_state != new_state THEN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the old state count if record exists.
+ UPDATE lease4_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND state = old_state;
+ END IF;
+
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Increment the new state count if record exists.
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND state = new_state;
+
+ -- Insert new state record if it does not exist.
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat VALUES (new_subnet_id, new_state, 1);
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease4_ADEL_lease4_stat;
+DELIMITER $$
+CREATE PROCEDURE lease4_ADEL_lease4_stat(IN old_state TINYINT,
+ IN old_subnet_id INT UNSIGNED)
+BEGIN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the state count if record exists.
+ UPDATE lease4_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND old_state = state;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_AINS_lease6_stat;
+DELIMITER $$
+CREATE PROCEDURE lease6_AINS_lease6_stat(IN new_state TINYINT,
+ IN new_subnet_id INT UNSIGNED,
+ IN new_lease_type TINYINT)
+BEGIN
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Update the state count if it exists.
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND lease_type = new_lease_type
+ AND state = new_state;
+
+ -- Insert the state count record if it does not exist.
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat VALUES (new_subnet_id, new_lease_type, new_state, 1);
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_AUPD_lease6_stat;
+DELIMITER $$
+CREATE PROCEDURE lease6_AUPD_lease6_stat(IN old_state TINYINT,
+ IN old_subnet_id INT UNSIGNED,
+ IN old_lease_type TINYINT,
+ IN new_state TINYINT,
+ IN new_subnet_id INT UNSIGNED,
+ IN new_lease_type TINYINT)
+BEGIN
+ IF old_subnet_id != new_subnet_id OR
+ old_lease_type != new_lease_type OR
+ old_state != new_state THEN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the old state count if record exists.
+ UPDATE lease6_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND lease_type = old_lease_type
+ AND state = old_state;
+ END IF;
+
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND lease_type = new_lease_type
+ AND state = new_state;
+
+ -- Insert new state record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat
+ VALUES (new_subnet_id, new_lease_type, new_state, 1);
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_ADEL_lease6_stat;
+DELIMITER $$
+CREATE PROCEDURE lease6_ADEL_lease6_stat(IN old_state TINYINT,
+ IN old_subnet_id INT UNSIGNED,
+ IN old_lease_type TINYINT)
+BEGIN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease6_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND lease_type = old_lease_type
+ AND state = old_state;
+ END IF;
+END $$
+DELIMITER ;
+
+-- Create tables that contain the number of active leases. --
+
+DROP TABLE IF EXISTS lease4_stat_by_client_class;
+CREATE TABLE lease4_stat_by_client_class (
+ client_class VARCHAR(128) NOT NULL PRIMARY KEY,
+ leases BIGINT UNSIGNED NOT NULL
+) ENGINE = InnoDB;
+
+DROP TABLE IF EXISTS lease6_stat_by_client_class;
+CREATE TABLE lease6_stat_by_client_class (
+ client_class VARCHAR(128) NOT NULL,
+ lease_type TINYINT NOT NULL,
+ leases BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (client_class, lease_type),
+ CONSTRAINT fk_lease6_stat_by_client_class_lease_type FOREIGN KEY (lease_type)
+ REFERENCES lease6_types (lease_type)
+) ENGINE = InnoDB;
+
+-- Create procedures to be called for each row in after-event triggers for
+-- INSERT, UPDATE and DELETE on lease tables.
+
+DROP PROCEDURE IF EXISTS lease4_AINS_lease4_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease4_AINS_lease4_stat_by_client_class(IN new_state TINYINT,
+ IN new_user_context TEXT)
+BEGIN
+ -- Declarations
+ DECLARE client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ -- Ignore ERROR 3141 (22032) at line 1: Invalid JSON text in argument 1 to
+ -- function json_extract: "The document is empty." at position 0.
+ -- Ignore ERROR 4037 (HY000): Unexpected end of JSON text in argument 1 to function 'json_extract'
+ -- These situations are handled with a propagating NULL result from JSON_EXTRACT.
+ DECLARE CONTINUE HANDLER FOR 3141 BEGIN END;
+ DECLARE CONTINUE HANDLER FOR 4037 BEGIN END;
+
+ -- Only state 0 is needed for lease limiting.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ SET client_classes = JSON_EXTRACT(new_user_context, '$."ISC"."client-classes"');
+ SET length = JSON_LENGTH(client_classes);
+
+ -- Iterate through all the client classes and increment the lease count for each.
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(client_classes, CONCAT('\$[', i, ']')));
+
+ -- Upsert to increment the lease count.
+ UPDATE lease4_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class;
+ IF ROW_COUNT() = 0 THEN
+ INSERT INTO lease4_stat_by_client_class VALUES (class, 1);
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease4_AUPD_lease4_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease4_AUPD_lease4_stat_by_client_class(IN old_state TINYINT,
+ IN old_user_context TEXT,
+ IN new_state TINYINT,
+ IN new_user_context TEXT)
+BEGIN
+ -- Declarations
+ DECLARE old_client_classes TEXT;
+ DECLARE new_client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ SET old_client_classes = JSON_EXTRACT(old_user_context, '$."ISC"."client-classes"');
+ SET new_client_classes = JSON_EXTRACT(new_user_context, '$."ISC"."client-classes"');
+
+ IF old_state != new_state OR old_client_classes != new_client_classes THEN
+ -- Check if it's moving away from a counted state.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ SET length = JSON_LENGTH(old_client_classes);
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(old_client_classes, CONCAT('\$[', i, ']')));
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease4_stat_by_client_class SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE client_class = class;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+
+ -- Check if it's moving into a counted state.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ SET length = JSON_LENGTH(new_client_classes);
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(new_client_classes, CONCAT('\$[', i, ']')));
+
+ -- Upsert to increment the lease count.
+ UPDATE lease4_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class;
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat_by_client_class VALUES (class, 1);
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease4_ADEL_lease4_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease4_ADEL_lease4_stat_by_client_class(IN old_state TINYINT,
+ IN old_user_context TEXT)
+BEGIN
+ -- Declarations
+ DECLARE client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ -- Ignore ERROR 3141 (22032) at line 1: Invalid JSON text in argument 1 to
+ -- function json_extract: "The document is empty." at position 0.
+ -- Ignore ERROR 4037 (HY000): Unexpected end of JSON text in argument 1 to function 'json_extract'
+ -- These situations are handled with a propagating NULL result from JSON_EXTRACT.
+ DECLARE CONTINUE HANDLER FOR 3141 BEGIN END;
+ DECLARE CONTINUE HANDLER FOR 4037 BEGIN END;
+
+ -- Only state 0 is accounted for in lease limiting.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ SET client_classes = JSON_EXTRACT(old_user_context, '$."ISC"."client-classes"');
+ SET length = JSON_LENGTH(client_classes);
+
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(client_classes, CONCAT('\$[', i, ']')));
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease4_stat_by_client_class SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE client_class = class;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_AINS_lease6_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease6_AINS_lease6_stat_by_client_class(IN new_state TINYINT,
+ IN new_user_context TEXT,
+ IN new_lease_type TINYINT)
+BEGIN
+ -- Declarations
+ DECLARE client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ -- Ignore ERROR 3141 (22032) at line 1: Invalid JSON text in argument 1 to
+ -- function json_extract: "The document is empty." at position 0.
+ -- Ignore ERROR 4037 (HY000): Unexpected end of JSON text in argument 1 to function 'json_extract'
+ -- These situations are handled with a propagating NULL result from JSON_EXTRACT.
+ DECLARE CONTINUE HANDLER FOR 3141 BEGIN END;
+ DECLARE CONTINUE HANDLER FOR 4037 BEGIN END;
+
+ -- Only state 0 is needed for lease limiting.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ SET client_classes = JSON_EXTRACT(new_user_context, '$."ISC"."client-classes"');
+ SET length = JSON_LENGTH(client_classes);
+
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(client_classes, CONCAT('\$[', i, ']')));
+
+ -- Upsert to increment the lease count.
+ UPDATE lease6_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class AND lease_type = new_lease_type;
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat_by_client_class VALUES (class, new_lease_type, 1);
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_AUPD_lease6_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease6_AUPD_lease6_stat_by_client_class(IN old_state TINYINT,
+ IN old_user_context TEXT,
+ IN old_lease_type TINYINT,
+ IN new_state TINYINT,
+ IN new_user_context TEXT,
+ IN new_lease_type TINYINT)
+BEGIN
+ -- Declarations
+ DECLARE old_client_classes TEXT;
+ DECLARE new_client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ SET old_client_classes = JSON_EXTRACT(old_user_context, '$."ISC"."client-classes"');
+ SET new_client_classes = JSON_EXTRACT(new_user_context, '$."ISC"."client-classes"');
+
+ IF old_state != new_state OR old_client_classes != new_client_classes OR old_lease_type != new_lease_type THEN
+ -- Check if it's moving away from a counted state.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ SET length = JSON_LENGTH(old_client_classes);
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(old_client_classes, CONCAT('\$[', i, ']')));
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease6_stat_by_client_class SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE client_class = class AND lease_type = old_lease_type;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+
+ -- Check if it's moving into a counted state.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ SET length = JSON_LENGTH(new_client_classes);
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(new_client_classes, CONCAT('\$[', i, ']')));
+
+ -- Upsert to increment the lease count.
+ UPDATE lease6_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class AND lease_type = new_lease_type;
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat_by_client_class VALUES (class, new_lease_type, 1);
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_ADEL_lease6_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease6_ADEL_lease6_stat_by_client_class(IN old_state TINYINT,
+ IN old_user_context TEXT,
+ IN old_lease_type TINYINT)
+BEGIN
+ -- Declarations
+ DECLARE client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ -- Ignore ERROR 3141 (22032) at line 1: Invalid JSON text in argument 1 to
+ -- function json_extract: "The document is empty." at position 0.
+ -- Ignore ERROR 4037 (HY000): Unexpected end of JSON text in argument 1 to function 'json_extract'
+ -- These situations are handled with a propagating NULL result from JSON_EXTRACT.
+ DECLARE CONTINUE HANDLER FOR 3141 BEGIN END;
+ DECLARE CONTINUE HANDLER FOR 4037 BEGIN END;
+
+ -- Only state 0 is accounted for in lease limiting. But check both states to be consistent with lease6_stat.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ SET client_classes = JSON_EXTRACT(old_user_context, '$."ISC"."client-classes"');
+ SET length = JSON_LENGTH(client_classes);
+
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(client_classes, CONCAT('\$[', i, ']')));
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease6_stat_by_client_class SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE client_class = class AND lease_type = old_lease_type;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+END $$
+DELIMITER ;
+
+-- Recreate the after-event triggers for INSERT, UPDATE and DELETE on lease tables to call the --
+-- stored procedures above in pairs of two: for client classes and for subnets. --
+
+-- Function that establishes whether JSON functions are supported.
+-- They should be provided with MySQL>= 5.7, MariaDB >= 10.2.3.
+DROP FUNCTION IF EXISTS isJsonSupported;
+DELIMITER $$
+CREATE FUNCTION isJsonSupported()
+RETURNS BOOL
+DETERMINISTIC
+BEGIN
+ DECLARE dummy BOOL;
+ DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
+ RETURN false;
+
+ SELECT JSON_EXTRACT('{ "foo": 1 }', '$.foo') INTO dummy;
+ RETURN true;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease4_insert;
+DROP TRIGGER IF EXISTS lease4_AINS;
+DELIMITER $$
+CREATE TRIGGER lease4_AINS AFTER INSERT ON lease4 FOR EACH ROW
+BEGIN
+ CALL lease4_AINS_lease4_stat(NEW.state, NEW.subnet_id);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease4_AINS_lease4_stat_by_client_class(NEW.state, NEW.user_context);
+ END IF;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease4_update;
+DROP TRIGGER IF EXISTS lease4_AUPD;
+DELIMITER $$
+CREATE TRIGGER lease4_AUPD AFTER UPDATE ON lease4 FOR EACH ROW
+BEGIN
+ CALL lease4_AUPD_lease4_stat(OLD.state, OLD.subnet_id, NEW.state, NEW.subnet_id);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease4_AUPD_lease4_stat_by_client_class(OLD.state, OLD.user_context, NEW.state, NEW.user_context);
+ END IF;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease4_delete;
+DROP TRIGGER IF EXISTS lease4_ADEL;
+DELIMITER $$
+CREATE TRIGGER lease4_ADEL AFTER DELETE ON lease4 FOR EACH ROW
+BEGIN
+ CALL lease4_ADEL_lease4_stat(OLD.state, OLD.subnet_id);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease4_ADEL_lease4_stat_by_client_class(OLD.state, OLD.user_context);
+ END IF;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease6_insert;
+DROP TRIGGER IF EXISTS lease6_AINS;
+DELIMITER $$
+CREATE TRIGGER lease6_AINS AFTER INSERT ON lease6 FOR EACH ROW
+BEGIN
+ CALL lease6_AINS_lease6_stat(NEW.state, NEW.subnet_id, NEW.lease_type);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease6_AINS_lease6_stat_by_client_class(NEW.state, NEW.user_context, NEW.lease_type);
+ END IF;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease6_update;
+DROP TRIGGER IF EXISTS lease6_AUPD;
+DELIMITER $$
+CREATE TRIGGER lease6_AUPD AFTER UPDATE ON lease6 FOR EACH ROW
+BEGIN
+ CALL lease6_AUPD_lease6_stat(OLD.state, OLD.subnet_id, OLD.lease_type, NEW.state, NEW.subnet_id, NEW.lease_type);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease6_AUPD_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type, NEW.state, NEW.user_context, NEW.lease_type);
+ END IF;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease6_delete;
+DROP TRIGGER IF EXISTS lease6_ADEL;
+DELIMITER $$
+CREATE TRIGGER lease6_ADEL AFTER DELETE ON lease6 FOR EACH ROW
+BEGIN
+ CALL lease6_ADEL_lease6_stat(OLD.state, OLD.subnet_id, OLD.lease_type);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease6_ADEL_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type);
+ END IF;
+END $$
+DELIMITER ;
+
+-- Create functions that check if the lease limits set in the given user context are exceeded.
+-- They return a string describing a limit that is being exceeded, or an empty
+-- string if no limits are exceeded. The following format for is assumed for user_context
+-- (not all nodes are mandatory and values are given only as examples):
+-- { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+-- "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+
+DROP FUNCTION IF EXISTS checkLease4Limits;
+DELIMITER $$
+CREATE FUNCTION checkLease4Limits(user_context TEXT)
+RETURNS TEXT
+READS SQL DATA
+BEGIN
+ -- Declarations
+ DECLARE json_element TEXT;
+ DECLARE length INT;
+ DECLARE class TEXT;
+ DECLARE name VARCHAR(255);
+ DECLARE i INT;
+ DECLARE lease_limit INT;
+ DECLARE lease_count INT;
+
+ -- Dive into client class limits.
+ SET json_element = JSON_EXTRACT(user_context, '$."ISC"."limits"."client-classes"');
+ SET length = JSON_LENGTH(json_element);
+
+ SET i = 0;
+ label: WHILE i < length DO
+ -- Get the lease limit for this client class.
+ SET class = JSON_EXTRACT(json_element, CONCAT('\$[', i, ']'));
+ SET name = JSON_UNQUOTE(JSON_EXTRACT(class, '$.name'));
+ SET lease_limit = JSON_EXTRACT(class, '$."address-limit"');
+
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SET lease_count = (SELECT leases FROM lease4_stat_by_client_class WHERE client_class = name);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+
+ -- Dive into subnet limits. Reuse i as subnet ID.
+ SET json_element = JSON_EXTRACT(user_context, '$.ISC.limits.subnet');
+ SET i = JSON_EXTRACT(json_element, '$.id');
+ SET lease_limit = JSON_EXTRACT(json_element, '$."address-limit"');
+
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SET lease_count = (SELECT leases FROM lease4_stat WHERE subnet_id = i AND state = 0);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for subnet ID ', i, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ RETURN '';
+END $$
+DELIMITER ;
+
+DROP FUNCTION IF EXISTS checkLease6Limits;
+DELIMITER $$
+CREATE FUNCTION checkLease6Limits(user_context TEXT)
+RETURNS TEXT
+READS SQL DATA
+BEGIN
+ -- Declarations
+ DECLARE json_element TEXT;
+ DECLARE length INT;
+ DECLARE class TEXT;
+ DECLARE name VARCHAR(255);
+ DECLARE i INT;
+ DECLARE lease_limit INT;
+ DECLARE lease_count INT;
+
+ -- Dive into client class limits.
+ SET json_element = JSON_EXTRACT(user_context, '$."ISC"."limits"."client-classes"');
+ SET length = JSON_LENGTH(json_element);
+
+ SET i = 0;
+ label: WHILE i < length DO
+ -- Get the lease limit for this client class.
+ SET class = JSON_EXTRACT(json_element, CONCAT('\$[', i, ']'));
+ SET name = JSON_UNQUOTE(JSON_EXTRACT(class, '$.name'));
+
+ SET lease_limit = JSON_EXTRACT(class, '$."address-limit"');
+ IF lease_limit IS NOT NULL THEN
+ -- Get the address count for this client class.
+ SET lease_count = (SELECT leases FROM lease6_stat_by_client_class WHERE client_class = name AND lease_type = 0);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ SET lease_limit = JSON_EXTRACT(class, '$."prefix-limit"');
+ IF lease_limit IS NOT NULL THEN
+ -- Get the prefix count for this client class.
+ SET lease_count = (SELECT leases FROM lease6_stat_by_client_class WHERE client_class = name AND lease_type = 2);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('prefix limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+
+ -- Dive into subnet limits. Reuse i as subnet ID.
+ SET json_element = JSON_EXTRACT(user_context, '$.ISC.limits.subnet');
+ SET i = JSON_EXTRACT(json_element, '$.id');
+ SET lease_limit = JSON_EXTRACT(json_element, '$."address-limit"');
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SET lease_count = (SELECT leases FROM lease6_stat WHERE subnet_id = i AND lease_type = 0 AND state = 0);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for subnet ID ', i, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+ SET lease_limit = JSON_EXTRACT(json_element, '$."prefix-limit"');
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SET lease_count = (SELECT leases FROM lease6_stat WHERE subnet_id = i AND lease_type = 2 AND state = 0);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('prefix limit ', lease_limit, ' for subnet ID ', i, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ RETURN '';
+END $$
+DELIMITER ;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '14', minor = '0';
+
+-- This line concludes the schema upgrade to version 14.
+
+# Notes:
+#
+# Indexes
+# =======
+# It is likely that additional indexes will be needed. However, the
+# increase in lookup performance from these will come at the expense
+# of a decrease in performance during insert operations due to the need
+# to update the indexes. For this reason, the need for additional indexes
+# will be determined by experiment during performance tests.
+#
+# The most likely additional indexes will cover the following columns:
+#
+# hwaddr and client_id
+# For lease stability: if a client requests a new lease, try to find an
+# existing or recently expired lease for it so that it can keep using the
+# same IP address.
+#
+# Field Sizes
+# ===========
+# If any of the VARxxx field sizes are altered, the lengths in the MySQL
+# backend source file (mysql_lease_mgr.cc) must be correspondingly changed.
+#
+# Portability
+# ===========
+# The 'ENGINE = INNODB' on some tables is not portable to another database
+# and will need to be removed.
+#
+# Some columns contain binary data so are stored as VARBINARY instead of
+# VARCHAR. This may be non-portable between databases: in this case, the
+# definition should be changed to VARCHAR.
diff --git a/src/share/database/scripts/mysql/dhcpdb_drop.mysql b/src/share/database/scripts/mysql/dhcpdb_drop.mysql
new file mode 100644
index 0000000..0d17890
--- /dev/null
+++ b/src/share/database/scripts/mysql/dhcpdb_drop.mysql
@@ -0,0 +1,153 @@
+# Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# Turn off foreign key checks as CASCADE isn't really supported in MySQL
+SET SESSION FOREIGN_KEY_CHECKS = 0;
+DROP TABLE IF EXISTS lease4;
+DROP TABLE IF EXISTS lease6;
+DROP TABLE IF EXISTS lease6_types;
+DROP TABLE IF EXISTS lease_hwaddr_source;
+DROP TABLE IF EXISTS schema_version;
+DROP TABLE IF EXISTS ipv6_reservations;
+DROP TABLE IF EXISTS hosts;
+DROP TABLE IF EXISTS dhcp4_options;
+DROP TABLE IF EXISTS dhcp6_options;
+DROP TABLE IF EXISTS host_identifier_type;
+DROP TABLE IF EXISTS lease_state;
+DROP TABLE IF EXISTS dhcp_option_scope;
+DROP TRIGGER IF EXISTS host_BDEL;
+DROP PROCEDURE IF EXISTS lease4DumpHeader;
+DROP PROCEDURE IF EXISTS lease4DumpData;
+DROP PROCEDURE IF EXISTS lease6DumpHeader;
+DROP PROCEDURE IF EXISTS lease6DumpData;
+DROP TRIGGER IF EXISTS stat_lease4_insert;
+DROP TRIGGER IF EXISTS stat_lease4_update;
+DROP TRIGGER IF EXISTS stat_lease4_delete;
+DROP TABLE IF EXISTS lease4_stat;
+DROP TRIGGER IF EXISTS stat_lease6_insert;
+DROP TRIGGER IF EXISTS stat_lease6_update;
+DROP TRIGGER IF EXISTS stat_lease6_delete;
+DROP TABLE IF EXISTS lease6_stat;
+DROP TABLE IF EXISTS logs;
+DROP TABLE IF EXISTS dhcp4_audit;
+DROP TABLE IF EXISTS dhcp4_audit_revision;
+DROP TABLE IF EXISTS dhcp4_global_parameter;
+DROP TABLE IF EXISTS dhcp4_global_parameter_server;
+DROP TABLE IF EXISTS dhcp4_option_def;
+DROP TABLE IF EXISTS dhcp4_option_def_server;
+DROP TABLE IF EXISTS dhcp4_options_server;
+DROP TABLE IF EXISTS dhcp4_pool;
+DROP TABLE IF EXISTS dhcp4_server;
+DROP TABLE IF EXISTS dhcp4_shared_network;
+DROP TABLE IF EXISTS dhcp4_shared_network_server;
+DROP TABLE IF EXISTS dhcp4_subnet;
+DROP TABLE IF EXISTS dhcp4_subnet_server;
+DROP TABLE IF EXISTS dhcp4_client_class;
+DROP TABLE IF EXISTS dhcp4_client_class_order;
+DROP TABLE IF EXISTS dhcp4_client_class_dependency;
+DROP TABLE IF EXISTS dhcp4_client_class_server;
+DROP TABLE IF EXISTS dhcp6_audit;
+DROP TABLE IF EXISTS dhcp6_global_parameter;
+DROP TABLE IF EXISTS dhcp6_global_parameter_server;
+DROP TABLE IF EXISTS dhcp6_option_def;
+DROP TABLE IF EXISTS dhcp6_option_def_server;
+DROP TABLE IF EXISTS dhcp6_options_server;
+DROP TABLE IF EXISTS dhcp6_pd_pool;
+DROP TABLE IF EXISTS dhcp6_pool;
+DROP TABLE IF EXISTS dhcp6_server;
+DROP TABLE IF EXISTS dhcp6_shared_network;
+DROP TABLE IF EXISTS dhcp6_shared_network_server;
+DROP TABLE IF EXISTS dhcp6_subnet;
+DROP TABLE IF EXISTS dhcp6_subnet_server;
+DROP TABLE IF EXISTS dhcp6_client_class;
+DROP TABLE IF EXISTS dhcp6_client_class_order;
+DROP TABLE IF EXISTS dhcp6_client_class_dependency;
+DROP TABLE IF EXISTS dhcp6_client_class_server;
+DROP TABLE IF EXISTS modification;
+DROP TABLE IF EXISTS parameter_data_type;
+DROP PROCEDURE IF EXISTS createAuditRevisionDHCP4;
+DROP PROCEDURE IF EXISTS createAuditEntryDHCP4;
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP4;
+DROP PROCEDURE IF EXISTS setClientClass4Order;
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassDependency;
+DROP PROCEDURE IF EXISTS updateDHCPv4ClientClassKnownDependency;
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassKnownDependencyChange;
+DROP TRIGGER IF EXISTS dhcp4_global_parameter_AINS;
+DROP TRIGGER IF EXISTS dhcp4_global_parameter_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_global_parameter_ADEL;
+DROP TRIGGER IF EXISTS dhcp4_subnet_AINS;
+DROP TRIGGER IF EXISTS dhcp4_subnet_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_subnet_ADEL;
+DROP TRIGGER IF EXISTS dhcp4_shared_network_AINS;
+DROP TRIGGER IF EXISTS dhcp4_shared_network_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_shared_network_ADEL;
+DROP TRIGGER IF EXISTS dhcp4_option_def_AINS;
+DROP TRIGGER IF EXISTS dhcp4_option_def_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_option_def_ADEL;
+DROP TRIGGER IF EXISTS dhcp4_options_AINS;
+DROP TRIGGER IF EXISTS dhcp4_options_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_options_ADEL;
+DROP TRIGGER IF EXISTS dhcp4_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp4_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_client_class_ADEL;
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_BINS;
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_AINS;
+DROP TABLE IF EXISTS dhcp6_audit_revision;
+DROP PROCEDURE IF EXISTS createAuditRevisionDHCP6;
+DROP PROCEDURE IF EXISTS createAuditEntryDHCP6;
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP6;
+DROP PROCEDURE IF EXISTS setClientClass6Order;
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassDependency;
+DROP PROCEDURE IF EXISTS updateDHCPv6ClientClassKnownDependency;
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassKnownDependencyChange;
+DROP TRIGGER IF EXISTS dhcp6_global_parameter_AINS;
+DROP TRIGGER IF EXISTS dhcp6_global_parameter_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_global_parameter_ADEL;
+DROP TRIGGER IF EXISTS dhcp6_subnet_AINS;
+DROP TRIGGER IF EXISTS dhcp6_subnet_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_subnet_ADEL;
+DROP TRIGGER IF EXISTS dhcp6_shared_network_AINS;
+DROP TRIGGER IF EXISTS dhcp6_shared_network_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_shared_network_ADEL;
+DROP TRIGGER IF EXISTS dhcp6_option_def_AINS;
+DROP TRIGGER IF EXISTS dhcp6_option_def_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_option_def_ADEL;
+DROP TRIGGER IF EXISTS dhcp6_options_AINS;
+DROP TRIGGER IF EXISTS dhcp6_options_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_options_ADEL;
+DROP TRIGGER IF EXISTS dhcp6_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp6_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_client_class_ADEL;
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_BINS;
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_AINS;
+DROP FUNCTION IF EXISTS colonSeparatedHex;
+DROP PROCEDURE IF EXISTS lease4Upload;
+DROP PROCEDURE IF EXISTS lease6Upload;
+DROP TRIGGER IF EXISTS dhcp4_shared_network_BDEL;
+DROP TRIGGER IF EXISTS dhcp6_shared_network_BDEL;
+DROP PROCEDURE IF EXISTS lease4_AINS_lease4_stat;
+DROP PROCEDURE IF EXISTS lease4_AUPD_lease4_stat;
+DROP PROCEDURE IF EXISTS lease4_ADEL_lease4_stat;
+DROP PROCEDURE IF EXISTS lease6_AINS_lease6_stat;
+DROP PROCEDURE IF EXISTS lease6_AUPD_lease6_stat;
+DROP PROCEDURE IF EXISTS lease6_ADEL_lease6_stat;
+DROP TABLE IF EXISTS lease4_stat_by_client_class;
+DROP TABLE IF EXISTS lease6_stat_by_client_class;
+DROP PROCEDURE IF EXISTS lease4_AINS_lease4_stat_by_client_class;
+DROP PROCEDURE IF EXISTS lease4_AUPD_lease4_stat_by_client_class;
+DROP PROCEDURE IF EXISTS lease4_ADEL_lease4_stat_by_client_class;
+DROP PROCEDURE IF EXISTS lease6_AINS_lease6_stat_by_client_class;
+DROP PROCEDURE IF EXISTS lease6_AUPD_lease6_stat_by_client_class;
+DROP PROCEDURE IF EXISTS lease6_ADEL_lease6_stat_by_client_class;
+DROP TRIGGER IF EXISTS lease4_AINS;
+DROP TRIGGER IF EXISTS lease4_AUPD;
+DROP TRIGGER IF EXISTS lease4_ADEL;
+DROP TRIGGER IF EXISTS lease6_AINS;
+DROP TRIGGER IF EXISTS lease6_AUPD;
+DROP TRIGGER IF EXISTS lease6_ADEL;
+DROP FUNCTION IF EXISTS checkLease4Limits;
+DROP FUNCTION IF EXISTS checkLease6Limits;
+DROP FUNCTION IF EXISTS isJsonSupported;
diff --git a/src/share/database/scripts/mysql/upgrade_001.0_to_002.0.sh.in b/src/share/database/scripts/mysql/upgrade_001.0_to_002.0.sh.in
new file mode 100644
index 0000000..efbc2c8
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_001.0_to_002.0.sh.in
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+# Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "1.0" ]; then
+ printf 'This script upgrades 1.0 to 2.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+ALTER TABLE lease6
+ ADD COLUMN hwaddr varbinary(20),
+ ADD COLUMN hwtype smallint unsigned,
+ ADD COLUMN hwaddr_source int unsigned;
+
+CREATE TABLE lease_hwaddr_source (
+ hwaddr_source INT PRIMARY KEY NOT NULL,
+ name VARCHAR(40)
+) ENGINE = INNODB;
+
+-- See src/lib/dhcp/dhcp/pkt.h for detailed explanation
+INSERT INTO lease_hwaddr_source VALUES (1, 'HWADDR_SOURCE_RAW');
+INSERT INTO lease_hwaddr_source VALUES (2, 'HWADDR_SOURCE_IPV6_LINK_LOCAL');
+INSERT INTO lease_hwaddr_source VALUES (4, 'HWADDR_SOURCE_DUID');
+INSERT INTO lease_hwaddr_source VALUES (8, 'HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION');
+INSERT INTO lease_hwaddr_source VALUES (16, 'HWADDR_SOURCE_REMOTE_ID');
+INSERT INTO lease_hwaddr_source VALUES (32, 'HWADDR_SOURCE_SUBSCRIBER_ID');
+INSERT INTO lease_hwaddr_source VALUES (64, 'HWADDR_SOURCE_DOCSIS');
+
+UPDATE schema_version SET version='2', minor='0';
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_002.0_to_003.0.sh.in b/src/share/database/scripts/mysql/upgrade_002.0_to_003.0.sh.in
new file mode 100644
index 0000000..78af24a
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_002.0_to_003.0.sh.in
@@ -0,0 +1,118 @@
+#!/bin/sh
+
+# Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "2.0" ]; then
+ printf 'This script upgrades 2.0 to 3.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+CREATE TABLE IF NOT EXISTS hosts (
+host_id INT UNSIGNED NOT NULL AUTO_INCREMENT ,
+dhcp_identifier VARBINARY(128) NOT NULL ,
+dhcp_identifier_type TINYINT NOT NULL ,
+dhcp4_subnet_id INT UNSIGNED NULL ,
+dhcp6_subnet_id INT UNSIGNED NULL ,
+ipv4_address INT UNSIGNED NULL ,
+hostname VARCHAR(255) NULL ,
+dhcp4_client_classes VARCHAR(255) NULL ,
+dhcp6_client_classes VARCHAR(255) NULL ,
+PRIMARY KEY (host_id) ,
+INDEX key_dhcp4_identifier_subnet_id (dhcp_identifier ASC, dhcp_identifier_type ASC) ,
+INDEX key_dhcp6_identifier_subnet_id (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp6_subnet_id ASC)
+) ENGINE = INNODB;
+
+CREATE TABLE IF NOT EXISTS ipv6_reservations (
+reservation_id INT NOT NULL AUTO_INCREMENT ,
+address VARCHAR(39) NOT NULL ,
+prefix_len TINYINT(3) UNSIGNED NOT NULL DEFAULT 128 ,
+type TINYINT(4) UNSIGNED NOT NULL DEFAULT 0 ,
+dhcp6_iaid INT UNSIGNED NULL ,
+host_id INT UNSIGNED NOT NULL ,
+PRIMARY KEY (reservation_id) ,
+INDEX fk_ipv6_reservations_host_idx (host_id ASC) ,
+CONSTRAINT fk_ipv6_reservations_Host
+FOREIGN KEY (host_id )
+REFERENCES hosts (host_id )
+ON DELETE NO ACTION
+ON UPDATE NO ACTION
+) ENGINE = INNODB;
+
+CREATE TABLE IF NOT EXISTS dhcp4_options (
+option_id INT UNSIGNED NOT NULL AUTO_INCREMENT ,
+code TINYINT UNSIGNED NOT NULL ,
+value BLOB NULL ,
+formatted_value TEXT NULL ,
+space VARCHAR(128) NULL ,
+persistent TINYINT(1) NOT NULL DEFAULT 0 ,
+dhcp_client_class VARCHAR(128) NULL ,
+dhcp4_subnet_id INT NULL ,
+host_id INT UNSIGNED NULL ,
+PRIMARY KEY (option_id) ,
+UNIQUE INDEX option_id_UNIQUE (option_id ASC) ,
+INDEX fk_options_host1_idx (host_id ASC) ,
+CONSTRAINT fk_options_host1
+FOREIGN KEY (host_id )
+REFERENCES hosts (host_id )
+ON DELETE NO ACTION
+ON UPDATE NO ACTION
+) ENGINE = INNODB;
+
+CREATE TABLE IF NOT EXISTS dhcp6_options (
+option_id INT UNSIGNED NOT NULL AUTO_INCREMENT ,
+code INT UNSIGNED NOT NULL ,
+value BLOB NULL ,
+formatted_value TEXT NULL ,
+space VARCHAR(128) NULL ,
+persistent TINYINT(1) NOT NULL DEFAULT 0 ,
+dhcp_client_class VARCHAR(128) NULL ,
+dhcp6_subnet_id INT NULL ,
+host_id INT UNSIGNED NULL ,
+PRIMARY KEY (option_id) ,
+UNIQUE INDEX option_id_UNIQUE (option_id ASC) ,
+INDEX fk_options_host1_idx (host_id ASC) ,
+CONSTRAINT fk_options_host10
+FOREIGN KEY (host_id )
+REFERENCES hosts (host_id )
+ON DELETE NO ACTION
+ON UPDATE NO ACTION
+) ENGINE = INNODB;
+
+DELIMITER $$
+CREATE TRIGGER host_BDEL BEFORE DELETE ON hosts FOR EACH ROW
+-- Edit trigger body code below this line. Do not edit lines above this one
+BEGIN
+DELETE FROM ipv6_reservations WHERE ipv6_reservations.host_id = OLD.host_id;
+END
+$$
+DELIMITER ;
+
+UPDATE schema_version SET version='3', minor='0';
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_003.0_to_004.0.sh.in b/src/share/database/scripts/mysql/upgrade_003.0_to_004.0.sh.in
new file mode 100644
index 0000000..3affd3a
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_003.0_to_004.0.sh.in
@@ -0,0 +1,173 @@
+#!/bin/sh
+
+# Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "3.0" ]; then
+ printf 'This script upgrades 3.0 to 4.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+# Add state column to the lease4 table.
+ALTER TABLE lease4
+ ADD COLUMN state INT UNSIGNED DEFAULT 0;
+
+# Add state column to the lease6 table.
+ALTER TABLE lease6
+ ADD COLUMN state INT UNSIGNED DEFAULT 0;
+
+# Create indexes for querying leases in a given state and segregated
+# by the expiration time. One of the applications is to retrieve all
+# expired leases. However, these indexes can be also used to retrieve
+# leases in a given state regardless of the expiration time.
+CREATE INDEX lease4_by_state_expire ON lease4 (state ASC, expire ASC);
+CREATE INDEX lease6_by_state_expire ON lease6 (state ASC, expire ASC);
+
+# Create table holding mapping of the lease states to their names.
+# This is not used in queries from the DHCP server but rather in
+# direct queries from the lease database management tools.
+CREATE TABLE IF NOT EXISTS lease_state (
+ state INT UNSIGNED PRIMARY KEY NOT NULL,
+ name VARCHAR(64) NOT NULL
+) ENGINE=INNODB;
+
+# Insert currently defined state names.
+INSERT INTO lease_state VALUES (0, 'default');
+INSERT INTO lease_state VALUES (1, 'declined');
+INSERT INTO lease_state VALUES (2, 'expired-reclaimed');
+
+# Add a constraint that any state value added to the lease4 must
+# map to a value in the lease_state table.
+ALTER TABLE lease4
+ ADD CONSTRAINT fk_lease4_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+# Add a constraint that any state value added to the lease6 must
+# map to a value in the lease_state table.
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+# Add a constraint that lease type in the lease6 table must map
+# to a lease type defined in the lease6_types table.
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_type FOREIGN KEY (lease_type)
+ REFERENCES lease6_types (lease_type);
+
+# Modify the name of one of the HW address sources, and add a new one.
+UPDATE lease_hwaddr_source
+ SET name = 'HWADDR_SOURCE_DOCSIS_CMTS'
+ WHERE hwaddr_source = 64;
+
+INSERT INTO lease_hwaddr_source VALUES (128, 'HWADDR_SOURCE_DOCSIS_MODEM');
+
+# Add UNSIGNED to match with the lease6.
+ALTER TABLE lease_hwaddr_source
+ MODIFY COLUMN hwaddr_source INT UNSIGNED NOT NULL DEFAULT 0;
+
+# Add a constraint that non-null hwaddr_source in the lease6 table
+# must map to an entry in the lease_hwaddr_source.
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_hwaddr_source FOREIGN KEY (hwaddr_source)
+ REFERENCES lease_hwaddr_source (hwaddr_source);
+
+# FUNCTION that returns a result set containing the column names for lease4 dumps
+DROP PROCEDURE IF EXISTS lease4DumpHeader;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpHeader()
+BEGIN
+SELECT 'address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state';
+END $$
+DELIMITER ;
+
+# FUNCTION that returns a result set containing the data for lease4 dumps
+DROP PROCEDURE IF EXISTS lease4DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpData()
+BEGIN
+SELECT
+ INET_NTOA(l.address),
+ IFNULL(HEX(l.hwaddr), ''),
+ IFNULL(HEX(l.client_id), ''),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd,
+ l.fqdn_rev,
+ l.hostname,
+ s.name
+from
+ lease4 l
+ LEFT OUTER JOIN lease_state s on (l.state = s.state);
+END $$
+DELIMITER ;
+
+# FUNCTION that returns a result set containing the column names for lease6 dumps
+DROP PROCEDURE IF EXISTS lease6DumpHeader;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpHeader()
+BEGIN
+SELECT 'address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,hwtype,hwaddr_source,state';
+END $$
+DELIMITER ;
+
+# FUNCTION that returns a result set containing the data for lease6 dumps
+DROP PROCEDURE IF EXISTS lease6DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpData()
+BEGIN
+SELECT
+ l.address,
+ IFNULL(HEX(l.duid), ''),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ IFNULL(t.name, ''),
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd,
+ l.fqdn_rev,
+ l.hostname,
+ IFNULL(HEX(l.hwaddr), ''),
+ IFNULL(l.hwtype, ''),
+ IFNULL(h.name, ''),
+ IFNULL(s.name, '')
+FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state)
+ left outer join lease_hwaddr_source h on (l.hwaddr_source = h.hwaddr_source);
+END $$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '4', minor = '0';
+# This line concludes database upgrade to version 4.0.
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_004.0_to_004.1.sh.in b/src/share/database/scripts/mysql/upgrade_004.0_to_004.1.sh.in
new file mode 100644
index 0000000..65a57ed
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_004.0_to_004.1.sh.in
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+# Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "4.0" ]; then
+ printf 'This script upgrades 4.0 to 4.1. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# In the event hardware address cannot be determined, we need to satisfy
+# foreign key constraint between lease6 and lease_hardware_source
+INSERT INTO lease_hwaddr_source VALUES (0, 'HWADDR_SOURCE_UNKNOWN');
+
+#
+# Add order by lease address to lease4DumpData
+#
+DROP PROCEDURE IF EXISTS lease4DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpData()
+BEGIN
+SELECT
+ INET_NTOA(l.address),
+ IFNULL(HEX(l.hwaddr), ''),
+ IFNULL(HEX(l.client_id), ''),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd,
+ l.fqdn_rev,
+ l.hostname,
+ s.name
+FROM
+ lease4 l
+ LEFT OUTER JOIN lease_state s on (l.state = s.state)
+ORDER BY l.address;
+END $$
+DELIMITER ;
+
+#
+# Add order by lease address to lease6DumpData
+#
+DROP PROCEDURE IF EXISTS lease6DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpData()
+BEGIN
+SELECT
+ l.address,
+ IFNULL(HEX(l.duid), ''),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ IFNULL(t.name, ''),
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd,
+ l.fqdn_rev,
+ l.hostname,
+ IFNULL(HEX(l.hwaddr), ''),
+ IFNULL(l.hwtype, ''),
+ IFNULL(h.name, ''),
+ IFNULL(s.name, '')
+FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state)
+ left outer join lease_hwaddr_source h on (l.hwaddr_source = h.hwaddr_source)
+ORDER BY l.address;
+END $$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '4', minor = '1';
+# This line concludes database upgrade to version 4.1.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_004.1_to_005.0.sh.in b/src/share/database/scripts/mysql/upgrade_004.1_to_005.0.sh.in
new file mode 100644
index 0000000..cfe3489
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_004.1_to_005.0.sh.in
@@ -0,0 +1,128 @@
+#!/bin/sh
+
+# Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "4.1" ]; then
+ printf 'This script upgrades 4.1 to 5.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Update index used for searching DHCPv4 reservations by identifier and subnet id.
+# This index is now unique (to prevent duplicates) and includes DHCPv4 subnet
+# identifier.
+DROP INDEX key_dhcp4_identifier_subnet_id ON hosts;
+CREATE UNIQUE INDEX key_dhcp4_identifier_subnet_id ON hosts (dhcp_identifier ASC , dhcp_identifier_type ASC , dhcp4_subnet_id ASC);
+
+# Update index used for searching DHCPv6 reservations by identifier and subnet id.
+# This index is now unique to prevent duplicates.
+DROP INDEX key_dhcp6_identifier_subnet_id ON hosts;
+CREATE UNIQUE INDEX key_dhcp6_identifier_subnet_id ON hosts (dhcp_identifier ASC , dhcp_identifier_type ASC , dhcp6_subnet_id ASC);
+
+# Create index to search for reservations using IP address and subnet id.
+# This unique index guarantees that there is only one occurrence of the
+# particular IPv4 address for a given subnet.
+CREATE UNIQUE INDEX key_dhcp4_ipv4_address_subnet_id ON hosts (ipv4_address ASC , dhcp4_subnet_id ASC);
+
+# Create index to search for reservations using address/prefix and prefix
+# length.
+CREATE UNIQUE INDEX key_dhcp6_address_prefix_len ON ipv6_reservations (address ASC , prefix_len ASC);
+
+# Create a table mapping host identifiers to their names. Values in this
+# table are used as a foreign key in hosts table to guarantee that only
+# identifiers present in host_identifier_type table are used in hosts
+# table.
+CREATE TABLE IF NOT EXISTS host_identifier_type (
+ type TINYINT PRIMARY KEY NOT NULL, # Lease type code.
+ name VARCHAR(32) # Name of the lease type
+) ENGINE = INNODB;
+
+START TRANSACTION;
+INSERT INTO host_identifier_type VALUES (0, 'hw-address'); # Non-temporary v6 addresses
+INSERT INTO host_identifier_type VALUES (1, 'duid'); # Temporary v6 addresses
+INSERT INTO host_identifier_type VALUES (2, 'circuit-id'); # Prefix delegations
+COMMIT;
+
+# Add a constraint that any identifier type value added to the hosts
+# must map to a value in the host_identifier_type table.
+ALTER TABLE hosts
+ ADD CONSTRAINT fk_host_identifier_type FOREIGN KEY (dhcp_identifier_type)
+ REFERENCES host_identifier_type (type);
+
+# Store DHCPv6 option code as 16-bit unsigned integer.
+ALTER TABLE dhcp6_options MODIFY code SMALLINT UNSIGNED NOT NULL;
+
+# Subnet identifier is unsigned.
+ALTER TABLE dhcp4_options MODIFY dhcp4_subnet_id INT UNSIGNED NULL;
+ALTER TABLE dhcp6_options MODIFY dhcp6_subnet_id INT UNSIGNED NULL;
+
+# Scopes associate DHCP options stored in dhcp4_options and
+# dhcp6_options tables with hosts, subnets, classes or indicate
+# that they are global options.
+CREATE TABLE IF NOT EXISTS dhcp_option_scope (
+ scope_id TINYINT UNSIGNED PRIMARY KEY NOT NULL,
+ scope_name VARCHAR(32)
+) ENGINE = INNODB;
+
+START TRANSACTION;
+INSERT INTO dhcp_option_scope VALUES (0, 'global');
+INSERT INTO dhcp_option_scope VALUES (1, 'subnet');
+INSERT INTO dhcp_option_scope VALUES (2, 'client-class');
+INSERT INTO dhcp_option_scope VALUES (3, 'host');
+COMMIT;
+
+# Add scopes into table holding DHCPv4 options
+ALTER TABLE dhcp4_options ADD COLUMN scope_id TINYINT UNSIGNED NOT NULL;
+ALTER TABLE dhcp4_options
+ ADD CONSTRAINT fk_dhcp4_option_scope FOREIGN KEY (scope_id)
+ REFERENCES dhcp_option_scope (scope_id);
+
+# Add scopes into table holding DHCPv6 options
+ALTER TABLE dhcp6_options ADD COLUMN scope_id TINYINT UNSIGNED NOT NULL;
+ALTER TABLE dhcp6_options
+ ADD CONSTRAINT fk_dhcp6_option_scope FOREIGN KEY (scope_id)
+ REFERENCES dhcp_option_scope (scope_id);
+
+# Add UNSIGNED to reservation_id
+ALTER TABLE ipv6_reservations
+ MODIFY reservation_id INT UNSIGNED NOT NULL AUTO_INCREMENT;
+
+# Add columns holding reservations for siaddr, sname and file fields
+# carried within DHCPv4 message.
+ALTER TABLE hosts ADD COLUMN dhcp4_next_server INT UNSIGNED NULL;
+ALTER TABLE hosts ADD COLUMN dhcp4_server_hostname VARCHAR(64) NULL;
+ALTER TABLE hosts ADD COLUMN dhcp4_boot_file_name VARCHAR(128) NULL;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '5', minor = '0';
+# This line concludes database upgrade to version 5.0.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_005.0_to_005.1.sh.in b/src/share/database/scripts/mysql/upgrade_005.0_to_005.1.sh.in
new file mode 100644
index 0000000..45f40b9
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_005.0_to_005.1.sh.in
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+# Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "5.0" ]; then
+ printf 'This script upgrades 5.0 to 5.1. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Add missing 'client-id' and new 'flex-id' host identifier types.
+INSERT INTO host_identifier_type VALUES (3, 'client-id');
+INSERT INTO host_identifier_type VALUES (4, 'flex-id');
+
+# Recreate the trigger removing dependent host entries.
+DROP TRIGGER host_BDEL;
+
+DELIMITER $$
+CREATE TRIGGER host_BDEL BEFORE DELETE ON hosts FOR EACH ROW
+-- Edit trigger body code below this line. Do not edit lines above this one
+BEGIN
+DELETE FROM ipv6_reservations WHERE ipv6_reservations.host_id = OLD.host_id;
+DELETE FROM dhcp4_options WHERE dhcp4_options.host_id = OLD.host_id;
+DELETE FROM dhcp6_options WHERE dhcp6_options.host_id = OLD.host_id;
+END
+$$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '5', minor = '1';
+# This line concludes database upgrade to version 5.1.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_005.1_to_005.2.sh.in b/src/share/database/scripts/mysql/upgrade_005.1_to_005.2.sh.in
new file mode 100644
index 0000000..4987878
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_005.1_to_005.2.sh.in
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+# Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "5.1" ]; then
+ printf 'This script upgrades 5.1 to 5.2. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Make subnet_id column types consistent with lease table columns
+ALTER TABLE dhcp4_options MODIFY dhcp4_subnet_id INT UNSIGNED;
+ALTER TABLE dhcp6_options MODIFY dhcp6_subnet_id INT UNSIGNED;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '5', minor = '2';
+# This line concludes database upgrade to version 5.2.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_005.2_to_006.0.sh.in b/src/share/database/scripts/mysql/upgrade_005.2_to_006.0.sh.in
new file mode 100644
index 0000000..63392c8
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_005.2_to_006.0.sh.in
@@ -0,0 +1,216 @@
+#!/bin/sh
+
+# Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "5.2" ]; then
+ printf 'This script upgrades 5.2 to 6.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Add user context into table holding hosts
+ALTER TABLE hosts ADD COLUMN user_context TEXT NULL;
+
+# Add user contexts into tables holding DHCP options
+ALTER TABLE dhcp4_options ADD COLUMN user_context TEXT NULL;
+ALTER TABLE dhcp6_options ADD COLUMN user_context TEXT NULL;
+
+# Create index for searching leases by subnet identifier.
+CREATE INDEX lease4_by_subnet_id ON lease4 (subnet_id);
+
+# Create for searching leases by subnet identifier and lease type.
+CREATE INDEX lease6_by_subnet_id_lease_type ON lease6 (subnet_id, lease_type);
+
+# The index by iaid_subnet_id_duid is not the best choice because there are
+# cases when we don't specify subnet identifier while searching leases. The
+# index will be universal if the subnet_id is the right most column in the
+# index.
+DROP INDEX lease6_by_iaid_subnet_id_duid on lease6;
+CREATE INDEX lease6_by_duid_iaid_subnet_id ON lease6 (duid, iaid, subnet_id);
+
+# Create lease4_stat table
+CREATE TABLE lease4_stat (
+ subnet_id INT UNSIGNED NOT NULL,
+ state INT UNSIGNED NOT NULL,
+ leases BIGINT,
+ PRIMARY KEY (subnet_id, state)
+) ENGINE = INNODB;
+
+# Create stat_lease4_insert trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease4_insert AFTER INSERT ON lease4
+ FOR EACH ROW
+ BEGIN
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Update the state count if it exists
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ # Insert the state count record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat VALUES (NEW.subnet_id, NEW.state, 1);
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Create stat_lease4_update trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease4_update AFTER UPDATE ON lease4
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state != NEW.state THEN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the old state count if record exists
+ UPDATE lease4_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND state = OLD.state;
+ END IF;
+
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Increment the new state count if record exists
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ # Insert new state record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat VALUES (NEW.subnet_id, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Create stat_lease4_delete trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease4_delete AFTER DELETE ON lease4
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the state count if record exists
+ UPDATE lease4_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND OLD.state = state;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Create lease6_stat table
+CREATE TABLE lease6_stat (
+ subnet_id INT UNSIGNED NOT NULL,
+ lease_type INT UNSIGNED NOT NULL,
+ state INT UNSIGNED NOT NULL,
+ leases BIGINT,
+ PRIMARY KEY (subnet_id, lease_type, state)
+) ENGINE = INNODB;
+
+# Create stat_lease6_insert trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease6_insert AFTER INSERT ON lease6
+ FOR EACH ROW
+ BEGIN
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Update the state count if it exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE
+ subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ # Insert the state count record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat
+ VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Create stat_lease6_update trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease6_update AFTER UPDATE ON lease6
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state != NEW.state THEN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the old state count if record exists
+ UPDATE lease6_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ # Insert new state record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat
+ VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Create stat_lease6_delete trigger
+DELIMITER $$
+CREATE TRIGGER stat_lease6_delete AFTER DELETE ON lease6
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the state count if record exists
+ UPDATE lease6_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Populate lease4_stat table based on existing leases
+# We only care about assigned and declined states
+INSERT INTO lease4_stat (subnet_id, state, leases)
+ SELECT subnet_id, state, count(state)
+ FROM lease4 WHERE state < 2
+ GROUP BY subnet_id, state ORDER BY subnet_id;
+
+# Populate lease6_stat table based on existing leases
+# We only care about assigned and declined states
+INSERT INTO lease6_stat (subnet_id, lease_type, state, leases)
+ SELECT subnet_id, lease_type, state, count(state)
+ FROM lease6 WHERE state < 2
+ GROUP BY subnet_id, lease_type, state
+ ORDER BY subnet_id;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '6', minor = '0';
+# This line concludes database upgrade to version 6.0.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_006.0_to_007.0.sh.in b/src/share/database/scripts/mysql/upgrade_006.0_to_007.0.sh.in
new file mode 100644
index 0000000..5d83da5
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_006.0_to_007.0.sh.in
@@ -0,0 +1,664 @@
+#!/bin/sh
+
+# Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "6.0" ]; then
+ printf 'This script upgrades 6.0 to 7.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Add user context into tables holding leases
+ALTER TABLE lease4 ADD COLUMN user_context TEXT NULL;
+ALTER TABLE lease6 ADD COLUMN user_context TEXT NULL;
+
+DROP PROCEDURE IF EXISTS lease4DumpHeader;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpHeader()
+BEGIN
+SELECT 'address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context';
+END $$
+DELIMITER ;
+
+# FUNCTION that returns a result set containing the data for lease4 dumps
+DROP PROCEDURE IF EXISTS lease4DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpData()
+BEGIN
+SELECT
+ INET_NTOA(l.address),
+ IFNULL(HEX(l.hwaddr), ''),
+ IFNULL(HEX(l.client_id), ''),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd,
+ l.fqdn_rev,
+ l.hostname,
+ s.name,
+ IFNULL(l.user_context, '')
+FROM
+ lease4 l
+ LEFT OUTER JOIN lease_state s on (l.state = s.state)
+ORDER BY l.address;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6DumpHeader;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpHeader()
+BEGIN
+SELECT 'address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,hwtype,hwaddr_source,state,user_context';
+END $$
+DELIMITER ;
+
+# FUNCTION that returns a result set containing the data for lease6 dumps
+DROP PROCEDURE IF EXISTS lease6DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpData()
+BEGIN
+SELECT
+ l.address,
+ IFNULL(HEX(l.duid), ''),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ IFNULL(t.name, ''),
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd,
+ l.fqdn_rev,
+ l.hostname,
+ IFNULL(HEX(l.hwaddr), ''),
+ IFNULL(l.hwtype, ''),
+ IFNULL(h.name, ''),
+ IFNULL(s.name, ''),
+ IFNULL(l.user_context, '')
+FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state)
+ left outer join lease_hwaddr_source h on (l.hwaddr_source = h.hwaddr_source)
+ORDER BY l.address;
+END $$
+DELIMITER ;
+
+# Create logs table (logs table is used by forensic logging hook library)
+CREATE TABLE logs (
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, # creation timestamp
+ address VARCHAR(43) NULL, # address or prefix
+ log TEXT NOT NULL # the log itself
+ ) ENGINE = INNODB;
+
+# Create search index
+CREATE INDEX timestamp_index ON logs (timestamp);
+
+#add auth key for reconfiguration
+ALTER TABLE hosts
+ ADD COLUMN auth_key VARCHAR(16) NULL;
+
+# Convert subnet-id values of 0 to NULL
+UPDATE hosts SET dhcp4_subnet_id = NULL WHERE dhcp4_subnet_id = 0;
+UPDATE dhcp4_options SET dhcp4_subnet_id = NULL WHERE dhcp4_subnet_id = 0;
+UPDATE hosts SET dhcp6_subnet_id = NULL WHERE dhcp6_subnet_id = 0;
+UPDATE dhcp6_options SET dhcp6_subnet_id = NULL WHERE dhcp6_subnet_id = 0;
+
+# Add scope for shared network specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(4, "shared-network");
+
+# Add scope for pool specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(5, "pool");
+
+# Add scope for PD pool specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(6, "pd-pool");
+
+# Create table modification
+CREATE TABLE IF NOT EXISTS modification (
+ id TINYINT(3) NOT NULL,
+ modification_type VARCHAR(32) NOT NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB;
+
+INSERT INTO modification(id, modification_type)
+ VALUES(0, "create");
+
+INSERT INTO modification(id, modification_type)
+ VALUES(1, "update");
+
+INSERT INTO modification(id, modification_type)
+ VALUES(2, "delete");
+
+# Create table dhcp4_server
+#
+CREATE TABLE IF NOT EXISTS dhcp4_server (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ tag VARCHAR(64) NOT NULL,
+ description TEXT,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY dhcp4_server_tag_UNIQUE (tag),
+ KEY key_dhcp4_server_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+# Special server entry meaning "all servers". This refers to
+# the configuration entries owned by all servers.
+INSERT INTO dhcp4_server(id, tag, description, modification_ts)
+ VALUES(1, "all", "special type: all servers", NOW());
+
+# Create table dhcp4_audit
+#
+CREATE TABLE IF NOT EXISTS dhcp4_audit (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ object_type VARCHAR(256) NOT NULL,
+ object_id BIGINT(2) UNSIGNED NOT NULL,
+ modification_type TINYINT(1) NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ log_message TEXT,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_audit_by_modification_ts (modification_ts),
+ KEY fk_dhcp4_audit_modification_type (modification_type),
+ CONSTRAINT fk_dhcp4_audit_modification_type FOREIGN KEY (modification_type)
+ REFERENCES modification (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp4_global_parameter
+#
+CREATE TABLE IF NOT EXISTS dhcp4_global_parameter (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ value LONGTEXT NOT NULL,
+ modification_ts timestamp NOT NULL,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_global_parameter_modification_ts (modification_ts),
+ KEY key_dhcp4_global_parameter_name (name)
+) ENGINE=InnoDB;
+
+# Create table dhcp4_global_parameter_server
+# M-to-M cross-reference between global parameters and servers
+#
+CREATE TABLE IF NOT EXISTS dhcp4_global_parameter_server (
+ parameter_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (parameter_id, server_id),
+ KEY fk_dhcp4_global_parameter_server_server_id (server_id),
+ KEY key_dhcp4_global_parameter_server (modification_ts),
+ CONSTRAINT fk_dhcp4_global_parameter_server_parameter_id FOREIGN KEY (parameter_id)
+ REFERENCES dhcp4_global_parameter (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_global_parameter_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp4_option_def
+#
+CREATE TABLE IF NOT EXISTS dhcp4_option_def (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ code SMALLINT UNSIGNED NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ space VARCHAR(128) NOT NULL,
+ type TINYINT UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ is_array TINYINT(1) NOT NULL,
+ encapsulate VARCHAR(128) NOT NULL,
+ record_types VARCHAR(512) DEFAULT NULL,
+ user_context LONGTEXT,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_option_def_modification_ts (modification_ts),
+ KEY key_dhcp4_option_def_code_space (code, space)
+) ENGINE=InnoDB;
+
+# Create table dhcp4_option_def_server
+# M-to-M cross-reference between option definitions and servers
+#
+CREATE TABLE IF NOT EXISTS dhcp4_option_def_server (
+ option_def_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (option_def_id, server_id),
+ KEY fk_dhcp4_option_def_server_server_id_idx (server_id),
+ KEY key_dhcp4_option_def_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp4_option_def_server_option_def_id FOREIGN KEY (option_def_id)
+ REFERENCES dhcp4_option_def (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_option_def_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp4_shared_network
+#
+CREATE TABLE IF NOT EXISTS dhcp4_shared_network (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ match_client_id TINYINT(1) NOT NULL DEFAULT '1',
+ modification_ts TIMESTAMP NOT NULL,
+ rebind_timer INT(10) DEFAULT NULL,
+ relay LONGTEXT,
+ renew_timer INT(10) DEFAULT NULL,
+ require_client_classes LONGTEXT DEFAULT NULL,
+ reservation_mode TINYINT(3) NOT NULL DEFAULT '3',
+ user_context LONGTEXT,
+ valid_lifetime INT(10) DEFAULT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp4_shared_network_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+# Create table dhcp4_shared_network_server
+# M-to-M cross-reference between shared networks and servers
+#
+CREATE TABLE IF NOT EXISTS dhcp4_shared_network_server (
+ shared_network_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (shared_network_id, server_id),
+ KEY key_dhcp4_shared_network_server_modification_ts (modification_ts),
+ KEY fk_dhcp4_shared_network_server_server_id (server_id),
+ CONSTRAINT fk_dhcp4_shared_network_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_shared_network_server_shared_network_id FOREIGN KEY (shared_network_id)
+ REFERENCES dhcp4_shared_network (id) ON DELETE CASCADE ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp4_subnet
+#
+CREATE TABLE IF NOT EXISTS dhcp4_subnet (
+ subnet_id INT(10) UNSIGNED NOT NULL,
+ subnet_prefix VARCHAR(32) NOT NULL,
+ 4o6_interface VARCHAR(128) DEFAULT NULL,
+ 4o6_interface_id VARCHAR(128) DEFAULT NULL,
+ 4o6_subnet VARCHAR(64) DEFAULT NULL,
+ boot_file_name VARCHAR(512) DEFAULT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ match_client_id TINYINT(1) NOT NULL DEFAULT '1',
+ modification_ts TIMESTAMP NOT NULL,
+ next_server INT(10) UNSIGNED DEFAULT NULL,
+ rebind_timer INT(10) DEFAULT NULL,
+ relay LONGTEXT,
+ renew_timer INT(10) DEFAULT NULL,
+ require_client_classes LONGTEXT DEFAULT NULL,
+ reservation_mode TINYINT(3) NOT NULL DEFAULT '3',
+ server_hostname VARCHAR(512) DEFAULT NULL,
+ shared_network_name VARCHAR(128) DEFAULT NULL,
+ user_context LONGTEXT,
+ valid_lifetime INT(10) DEFAULT NULL,
+ PRIMARY KEY (subnet_id),
+ UNIQUE KEY subnet4_subnet_prefix (subnet_prefix),
+ KEY fk_dhcp4_subnet_shared_network (shared_network_name),
+ KEY key_dhcp4_subnet_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp4_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network (name)
+ ON DELETE SET NULL ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp4_pool
+#
+CREATE TABLE IF NOT EXISTS dhcp4_pool (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ start_address INT(10) UNSIGNED NOT NULL,
+ end_address INT(10) UNSIGNED NOT NULL,
+ subnet_id INT(10) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_pool_modification_ts (modification_ts),
+ KEY fk_dhcp4_pool_subnet_id (subnet_id),
+ CONSTRAINT fk_dhcp4_pool_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp4_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB;
+
+# Create table dhcp4_subnet_server
+# M-to-M cross-reference between subnets and servers
+#
+CREATE TABLE IF NOT EXISTS dhcp4_subnet_server (
+ subnet_id INT(10) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (subnet_id,server_id),
+ KEY fk_dhcp4_subnet_server_server_id_idx (server_id),
+ KEY key_dhcp4_subnet_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp4_subnet_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_subnet_server_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp4_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+
+# Modify the primary key to BINGINT as other tables have.
+#
+ALTER TABLE dhcp4_options MODIFY option_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+# Add configuration backend specific columns.
+ALTER TABLE dhcp4_options
+ ADD COLUMN shared_network_name VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN pool_id BIGINT(20) UNSIGNED DEFAULT NULL,
+ ADD COLUMN modification_ts TIMESTAMP NOT NULL;
+
+# Create table dhcp4_options_server
+# M-to-M cross-reference between options and servers
+#
+CREATE TABLE IF NOT EXISTS dhcp4_options_server (
+ option_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (option_id, server_id),
+ KEY fk_dhcp4_options_server_server_id (server_id),
+ KEY key_dhcp4_options_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp4_options_server_option_id FOREIGN KEY (option_id)
+ REFERENCES dhcp4_options (option_id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_options_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create trigger which removes pool specific options upon removal of
+# the pool.
+DELIMITER $$
+CREATE TRIGGER dhcp4_pool_BDEL BEFORE DELETE ON dhcp4_pool FOR EACH ROW
+-- Edit trigger body code below this line. Do not edit lines above this one
+BEGIN
+DELETE FROM dhcp4_options WHERE scope_id = 5 AND pool_id = OLD.id;
+END
+$$
+DELIMITER ;
+
+# Create table dhcp6_server
+#
+CREATE TABLE IF NOT EXISTS dhcp6_server (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ tag VARCHAR(64) NOT NULL,
+ description TEXT,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY dhcp6_server_tag_UNIQUE (tag),
+ KEY key_dhcp6_server_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+# Special server entry meaning "all servers". This refers to
+# the configuration entries owned by all servers.
+INSERT INTO dhcp6_server(id, tag, description, modification_ts)
+ VALUES(1, "all", "special type: all servers", NOW());
+
+# Create table dhcp6_audit
+#
+CREATE TABLE IF NOT EXISTS dhcp6_audit (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ object_type VARCHAR(256) NOT NULL,
+ object_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_type TINYINT(1) NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ log_message TEXT,
+ PRIMARY KEY (id),
+ KEY key_dhcp6_audit_modification_ts (modification_ts),
+ KEY fk_dhcp6_audit_modification_type (modification_type),
+ CONSTRAINT fk_dhcp6_audit_modification_type FOREIGN KEY (modification_type)
+ REFERENCES modification (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp6_global_parameter
+#
+CREATE TABLE IF NOT EXISTS dhcp6_global_parameter (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ value LONGTEXT NOT NULL,
+ modification_ts timestamp NOT NULL,
+ PRIMARY KEY (id),
+ KEY key_dhcp6_global_parameter_modification_ts (modification_ts),
+ KEY key_dhcp6_global_parameter_name (name)
+) ENGINE=InnoDB;
+
+# Create table dhcp6_global_parameter_server
+# M-to-M cross-reference between global parameters and servers
+#
+CREATE TABLE IF NOT EXISTS dhcp6_global_parameter_server (
+ parameter_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (parameter_id, server_id),
+ KEY fk_dhcp6_global_parameter_server_server_id (server_id),
+ KEY key_dhcp6_global_parameter_server (modification_ts),
+ CONSTRAINT fk_dhcp6_global_parameter_server_parameter_id FOREIGN KEY (parameter_id)
+ REFERENCES dhcp6_global_parameter (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_global_parameter_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp6_option_def
+#
+CREATE TABLE IF NOT EXISTS dhcp6_option_def (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ code SMALLINT UNSIGNED NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ space VARCHAR(128) NOT NULL,
+ type TINYINT UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ is_array TINYINT(1) NOT NULL,
+ encapsulate VARCHAR(128) NOT NULL,
+ record_types VARCHAR(512) DEFAULT NULL,
+ user_context LONGTEXT,
+ PRIMARY KEY (id),
+ KEY key_dhcp6_option_def_modification_ts (modification_ts),
+ KEY key_dhcp6_option_def_code_space (code, space)
+) ENGINE=InnoDB;
+
+# Create table dhcp6_option_def_server
+# M-to-M cross-reference between option definitions and servers
+#
+CREATE TABLE IF NOT EXISTS dhcp6_option_def_server (
+ option_def_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (option_def_id, server_id),
+ KEY fk_dhcp6_option_def_server_server_id_idx (server_id),
+ KEY key_dhcp6_option_def_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_option_def_server_option_def_id FOREIGN KEY (option_def_id)
+ REFERENCES dhcp6_option_def (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_option_def_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp6_shared_network
+#
+CREATE TABLE dhcp6_shared_network (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ preferred_lifetime INT(10) DEFAULT NULL,
+ rapid_commit TINYINT(1) NOT NULL DEFAULT '1',
+ rebind_timer INT(10) DEFAULT NULL,
+ relay LONGTEXT DEFAULT NULL,
+ renew_timer INT(10) DEFAULT NULL,
+ require_client_classes LONGTEXT,
+ reservation_mode TINYINT(3) NOT NULL DEFAULT '3',
+ user_context LONGTEXT,
+ valid_lifetime INT(10) DEFAULT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp6_shared_network_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+# Create table dhcp6_shared_network_server
+# M-to-M cross-reference between shared networks and servers
+#
+CREATE TABLE IF NOT EXISTS dhcp6_shared_network_server (
+ shared_network_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ KEY key_dhcp6_shared_network_server_modification_ts (modification_ts),
+ KEY fk_dhcp6_shared_network_server_server_id_idx (server_id),
+ KEY fk_dhcp6_shared_network_server_shared_network_id (shared_network_id),
+ CONSTRAINT fk_dhcp6_shared_network_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_shared_network_server_shared_network_id FOREIGN KEY (shared_network_id)
+ REFERENCES dhcp6_shared_network (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp6_subnet
+#
+CREATE TABLE dhcp6_subnet (
+ subnet_id int(10) UNSIGNED NOT NULL,
+ subnet_prefix VARCHAR(64) NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ preferred_lifetime INT(10) DEFAULT NULL,
+ rapid_commit TINYINT(1) NOT NULL DEFAULT '1',
+ rebind_timer INT(10) DEFAULT NULL,
+ relay LONGTEXT DEFAULT NULL,
+ renew_timer INT(10) DEFAULT NULL,
+ require_client_classes LONGTEXT,
+ reservation_mode TINYINT(3) NOT NULL DEFAULT '3',
+ shared_network_name VARCHAR(128) DEFAULT NULL,
+ user_context LONGTEXT,
+ valid_lifetime INT(10) DEFAULT NULL,
+ PRIMARY KEY (subnet_id),
+ UNIQUE KEY subnet6_subnet_prefix (subnet_prefix),
+ KEY fk_dhcp6_subnet_shared_network (shared_network_name),
+ KEY key_dhcp6_subnet_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network (name)
+ ON DELETE SET NULL ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp6_subnet_server
+# M-to-M cross-reference between subnets and servers
+#
+CREATE TABLE dhcp6_subnet_server (
+ subnet_id INT(10) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (subnet_id, server_id),
+ KEY fk_dhcp6_subnet_server_server_id (server_id),
+ KEY key_dhcp6_subnet_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_subnet_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_subnet_server_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create table dhcp6_pd_pool
+#
+CREATE TABLE IF NOT EXISTS dhcp6_pd_pool (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ prefix VARCHAR(45) NOT NULL,
+ prefix_length TINYINT(3) NOT NULL,
+ delegated_prefix_length TINYINT(3) NOT NULL,
+ dhcp6_subnet_id INT(10) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ KEY fk_dhcp6_pd_pool_subnet_id (dhcp6_subnet_id),
+ KEY key_dhcp6_pd_pool_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_pd_pool_subnet_id FOREIGN KEY (dhcp6_subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB;
+
+# Create table dhcp6_pool
+#
+CREATE TABLE IF NOT EXISTS dhcp6_pool (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ start_address VARCHAR(45) NOT NULL,
+ end_address VARCHAR(45) NOT NULL,
+ dhcp6_subnet_id INT(10) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ KEY fk_dhcp6_pool_subnet_id (dhcp6_subnet_id),
+ KEY key_dhcp6_pool_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_pool_subnet_id FOREIGN KEY (dhcp6_subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB;
+
+# Modify the primary key to BINGINT as other tables have.
+ALTER TABLE dhcp6_options MODIFY option_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+# Add configuration backend specific columns.
+ALTER TABLE dhcp6_options
+ ADD COLUMN shared_network_name VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN pool_id BIGINT(20) UNSIGNED DEFAULT NULL,
+ ADD COLUMN pd_pool_id BIGINT(20) UNSIGNED DEFAULT NULL,
+ ADD COLUMN modification_ts TIMESTAMP NOT NULL;
+
+# Create table dhcp6_options_server
+# M-to-M cross-reference between options and servers
+#
+CREATE TABLE IF NOT EXISTS dhcp6_options_server (
+ option_id BIGINT(20) UNSIGNED NOT NULL,
+ server_id BIGINT(20) UNSIGNED NOT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (option_id, server_id),
+ KEY fk_dhcp6_options_server_server_id_idx (server_id),
+ KEY key_dhcp6_options_server_modification_ts (modification_ts),
+ CONSTRAINT fk_dhcp6_options_server_option_id FOREIGN KEY (option_id)
+ REFERENCES dhcp6_options (option_id)
+ ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_options_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE=InnoDB;
+
+# Create trigger which removes pool specific options upon removal of
+# the pool.
+DELIMITER $$
+CREATE TRIGGER dhcp6_pool_BDEL BEFORE DELETE ON dhcp6_pool FOR EACH ROW
+-- Edit trigger body code below this line. Do not edit lines above this one
+BEGIN
+DELETE FROM dhcp6_options WHERE scope_id = 5 AND pool_id = OLD.id;
+END
+$$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '7', minor = '0';
+
+# This line concludes database upgrade to version 7.0.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_007.0_to_008.0.sh.in b/src/share/database/scripts/mysql/upgrade_007.0_to_008.0.sh.in
new file mode 100644
index 0000000..bba08bc
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_007.0_to_008.0.sh.in
@@ -0,0 +1,996 @@
+#!/bin/sh
+
+# Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "7.0" ]; then
+ printf 'This script upgrades 7.0 to 8.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+
+ALTER TABLE dhcp4_options
+ MODIFY COLUMN modification_ts TIMESTAMP NOT NULL
+ DEFAULT CURRENT_TIMESTAMP;
+
+ALTER TABLE dhcp6_options
+ MODIFY COLUMN modification_ts TIMESTAMP NOT NULL
+ DEFAULT CURRENT_TIMESTAMP;
+
+ALTER TABLE dhcp4_subnet
+ ADD COLUMN authoritative TINYINT(1) DEFAULT NULL,
+ ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL,
+ ADD COLUMN t1_percent FLOAT DEFAULT NULL,
+ ADD COLUMN t2_percent FLOAT DEFAULT NULL;
+
+ALTER TABLE dhcp4_subnet
+ MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL;
+
+ALTER TABLE dhcp4_subnet
+ MODIFY COLUMN match_client_id TINYINT(1) DEFAULT NULL;
+
+ALTER TABLE dhcp4_shared_network
+ ADD COLUMN authoritative TINYINT(1) DEFAULT NULL,
+ ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL,
+ ADD COLUMN t1_percent FLOAT DEFAULT NULL,
+ ADD COLUMN t2_percent FLOAT DEFAULT NULL,
+ ADD COLUMN boot_file_name VARCHAR(512) DEFAULT NULL,
+ ADD COLUMN next_server INT(10) UNSIGNED DEFAULT NULL,
+ ADD COLUMN server_hostname VARCHAR(512) DEFAULT NULL;
+
+ALTER TABLE dhcp4_shared_network
+ MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL;
+
+ALTER TABLE dhcp4_shared_network
+ MODIFY COLUMN match_client_id TINYINT(1) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL,
+ ADD COLUMN t1_percent FLOAT DEFAULT NULL,
+ ADD COLUMN t2_percent FLOAT DEFAULT NULL,
+ ADD COLUMN interface_id VARCHAR(128) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ MODIFY COLUMN rapid_commit TINYINT(1) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ ADD COLUMN calculate_tee_times TINYINT(1) DEFAULT NULL,
+ ADD COLUMN t1_percent FLOAT DEFAULT NULL,
+ ADD COLUMN t2_percent FLOAT DEFAULT NULL,
+ ADD COLUMN interface_id VARCHAR(128) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ MODIFY COLUMN reservation_mode TINYINT(3) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ MODIFY COLUMN rapid_commit TINYINT(1) DEFAULT NULL;
+
+-- -----------------------------------------------------
+-- Make sure that constraints on the 7.0 schema tables
+-- have appropriate referential actions. All tables
+-- which join the configuration elements with the
+-- servers should perform cascade deletion.
+-- -----------------------------------------------------
+
+ALTER TABLE dhcp4_global_parameter_server
+ DROP FOREIGN KEY fk_dhcp4_global_parameter_server_server_id;
+
+ALTER TABLE dhcp4_global_parameter_server
+ ADD CONSTRAINT fk_dhcp4_global_parameter_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp4_option_def_server
+ DROP FOREIGN KEY fk_dhcp4_option_def_server_server_id;
+
+ALTER TABLE dhcp4_option_def_server
+ ADD CONSTRAINT fk_dhcp4_option_def_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp4_shared_network_server
+ DROP FOREIGN KEY fk_dhcp4_shared_network_server_server_id;
+
+ALTER TABLE dhcp4_shared_network_server
+ ADD CONSTRAINT fk_dhcp4_shared_network_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp4_subnet_server
+ DROP FOREIGN KEY fk_dhcp4_subnet_server_server_id;
+
+ALTER TABLE dhcp4_subnet_server
+ ADD CONSTRAINT fk_dhcp4_subnet_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp4_options_server
+ DROP FOREIGN KEY fk_dhcp4_options_server_server_id;
+
+ALTER TABLE dhcp4_options_server
+ ADD CONSTRAINT fk_dhcp4_options_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp6_global_parameter_server
+ DROP FOREIGN KEY fk_dhcp6_global_parameter_server_server_id;
+
+ALTER TABLE dhcp6_global_parameter_server
+ ADD CONSTRAINT fk_dhcp6_global_parameter_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp6_option_def_server
+ DROP FOREIGN KEY fk_dhcp6_option_def_server_server_id;
+
+ALTER TABLE dhcp6_option_def_server
+ ADD CONSTRAINT fk_dhcp6_option_def_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp6_shared_network_server
+ DROP FOREIGN KEY fk_dhcp6_shared_network_server_server_id;
+
+ALTER TABLE dhcp6_shared_network_server
+ ADD CONSTRAINT fk_dhcp6_shared_network_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp6_subnet_server
+ DROP FOREIGN KEY fk_dhcp6_subnet_server_server_id;
+
+ALTER TABLE dhcp6_subnet_server
+ ADD CONSTRAINT fk_dhcp6_subnet_server_server_id
+ FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+ALTER TABLE dhcp6_options_server
+ DROP FOREIGN KEY fk_dhcp6_options_server_option_id;
+
+ALTER TABLE dhcp6_options_server
+ ADD CONSTRAINT fk_dhcp6_options_server_option_id
+ FOREIGN KEY (option_id)
+ REFERENCES dhcp6_options (option_id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+-- -----------------------------------------------------
+-- Table dhcp4_audit_revision
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_audit_revision (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ modification_ts TIMESTAMP NOT NULL,
+ log_message TEXT,
+ server_id BIGINT(10) UNSIGNED,
+ PRIMARY KEY (id),
+ KEY key_dhcp4_audit_revision_by_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Drop columns from the dhcp4_audit table which now
+-- belong to the dhcp4_audit_revision.
+-- -----------------------------------------------------
+ALTER TABLE dhcp4_audit
+ DROP COLUMN modification_ts,
+ DROP COLUMN log_message;
+
+-- -----------------------------------------------------
+-- Add column revision_id and the foreign key with a
+-- reference to the dhcp4_audit_revision table.
+-- -----------------------------------------------------
+ALTER TABLE dhcp4_audit
+ ADD COLUMN revision_id BIGINT(20) UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp4_audit
+ ADD CONSTRAINT fk_dhcp4_audit_revision FOREIGN KEY (revision_id)
+ REFERENCES dhcp4_audit_revision (id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp4_audit_revision table and sets appropriate session
+-- variables to be used while creating the audit entries
+-- by triggers. This procedure should be called at the
+-- beginning of a transaction which modifies configuration
+-- data in the database, e.g. when new subnet is added.
+--
+-- Parameters:
+-- - audit_ts timestamp to be associated with the audit
+-- revision.
+-- - server_tag is used to retrieve the server_id which
+-- associates the changes applied with the particular
+-- server or all servers.
+-- - audit_log_message is a log message associates with
+-- the audit revision.
+-- - cascade_transaction is assigned to a session
+-- variable which is used in some triggers to determine
+-- if the audit entry should be created for them or
+-- not. Specifically, this is used when DHCP options
+-- are inserted, updated or deleted. If such modification
+-- is a part of the larger change (e.g. change in the
+-- subnet the options belong to) the dedicated audit
+-- entry for options must not be created. On the other
+-- hand, if the global option is being added, the
+-- audit entry for the option must be created because
+-- it is the sole object modified in that case.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createAuditRevisionDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createAuditRevisionDHCP4(IN audit_ts TIMESTAMP,
+ IN server_tag VARCHAR(256),
+ IN audit_log_message TEXT,
+ IN cascade_transaction TINYINT(1))
+BEGIN
+ DECLARE srv_id BIGINT(20);
+ IF @disable_audit IS NULL OR @disable_audit = 0 THEN
+ SELECT id INTO srv_id FROM dhcp4_server WHERE tag = server_tag;
+ INSERT INTO dhcp4_audit_revision (modification_ts, server_id, log_message)
+ VALUES (audit_ts, srv_id, audit_log_message);
+ SET @audit_revision_id = LAST_INSERT_ID();
+ SET @cascade_transaction = cascade_transaction;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp4_audit table. It should be called from the
+-- triggers of the tables where the config modifications
+-- are applied. The @audit_revision_id variable contains
+-- the revision id to be placed in the audit entries.
+--
+-- The following parameters are passed to this procedure:
+-- - object_type_val: name of the table to be associated
+-- with the applied changes.
+-- - object_id_val: identifier of the modified object in
+-- that table.
+-- - modification_type_val: string value indicating the
+-- type of the change, i.e. "create", "update" or
+-- "delete".
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- ----------------------------------------------------
+DROP PROCEDURE IF EXISTS createAuditEntryDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createAuditEntryDHCP4(IN object_type_val VARCHAR(256),
+ IN object_id_val BIGINT(20) UNSIGNED,
+ IN modification_type_val VARCHAR(32))
+BEGIN
+ IF @disable_audit IS NULL OR @disable_audit = 0 THEN
+ INSERT INTO dhcp4_audit (object_type, object_id, modification_type, revision_id)
+ VALUES (object_type_val, object_id_val, \
+ (SELECT id FROM modification WHERE modification_type = modification_type_val), \
+ @audit_revision_id);
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Triggers used to create entries in the audit
+-- tables upon insertion, update or deletion of the
+-- configuration entries.
+-- -----------------------------------------------------
+
+# Create dhcp4_global_parameter insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_global_parameter_AINS AFTER INSERT ON dhcp4_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_global_parameter', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_global_parameter update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_global_parameter_AUPD AFTER UPDATE ON dhcp4_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_global_parameter', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_global_parameter delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_global_parameter_ADEL AFTER DELETE ON dhcp4_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_global_parameter', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_subnet insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_subnet_AINS AFTER INSERT ON dhcp4_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_subnet', NEW.subnet_id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_subnet update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_subnet_AUPD AFTER UPDATE ON dhcp4_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_subnet', NEW.subnet_id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_subnet delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_subnet_ADEL AFTER DELETE ON dhcp4_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_subnet', OLD.subnet_id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_shared_network insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_shared_network_AINS AFTER INSERT ON dhcp4_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_shared_network update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_shared_network_AUPD AFTER UPDATE ON dhcp4_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_shared_network delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_shared_network_ADEL AFTER DELETE ON dhcp4_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_option_def insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_option_def_AINS AFTER INSERT ON dhcp4_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_option_def', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_option_def update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_option_def_AUPD AFTER UPDATE ON dhcp4_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_option_def', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_option_def delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_option_def_ADEL AFTER DELETE ON dhcp4_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_option_def', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates an audit entry for a
+-- DHCPv4 option. Depending on the scope of the option
+-- the audit entry can be created for various levels
+-- of configuration hierarchy. If this is a global
+-- option the audit entry is created for this option
+-- for CREATE, UPDATE or DELETE. If the option is being
+-- added for an owning option, e.g. for a subnet, the
+-- audit entry is created as an UPDATE to this object.
+-- From the Kea perspective such option addition will
+-- be seen as a subnet update and the server will fetch
+-- the whole subnet and merge it into its configuration.
+-- The audit entry is not created if it was already
+-- created as part of the current transaction.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createOptionAuditDHCP4(IN modification_type VARCHAR(32),
+ IN scope_id TINYINT(3) UNSIGNED,
+ IN option_id BIGINT(20) UNSIGNED,
+ IN subnet_id INT(10) UNSIGNED,
+ IN host_id INT(10) UNSIGNED,
+ IN network_name VARCHAR(128),
+ IN pool_id BIGINT(20))
+BEGIN
+ # These variables will hold shared network id and subnet id that
+ # we will select.
+ DECLARE snid VARCHAR(128);
+ DECLARE sid INT(10) UNSIGNED;
+
+ # Cascade transaction flag is set to 1 to prevent creation of
+ # the audit entries for the options when the options are
+ # created as part of the parent object creation or update.
+ # For example: when the option is added as part of the subnet
+ # addition, the cascade transaction flag is equal to 1. If
+ # the option is added into the existing subnet the cascade
+ # transaction is equal to 0. Note that depending on the option
+ # scope the audit entry will contain the object_type value
+ # of the parent object to cause the server to replace the
+ # entire subnet. The only case when the object_type will be
+ # set to 'dhcp4_options' is when a global option is added.
+ # Global options do not have the owner.
+ IF @cascade_transaction IS NULL OR @cascade_transaction = 0 THEN
+ # todo: host manager hasn't been updated to use audit
+ # mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ # If a global option is added or modified, create audit
+ # entry for the 'dhcp4_options' table.
+ CALL createAuditEntryDHCP4('dhcp4_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ # If subnet specific option is added or modified, create
+ # audit entry for the entire subnet, which indicates that
+ # it should be treated as the subnet update.
+ CALL createAuditEntryDHCP4('dhcp4_subnet', subnet_id, "update");
+ ELSEIF scope_id = 4 THEN
+ # If shared network specific option is added or modified,
+ # create audit entry for the shared network which
+ # indicates that it should be treated as the shared
+ # network update.
+ SELECT id INTO snid FROM dhcp4_shared_network WHERE name = network_name LIMIT 1;
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', snid, "update");
+ ELSEIF scope_id = 5 THEN
+ # If pool specific option is added or modified, create
+ # audit entry for the subnet which this pool belongs to.
+ SELECT dhcp4_pool.subnet_id INTO sid FROM dhcp4_pool WHERE id = pool_id;
+ CALL createAuditEntryDHCP4('dhcp4_subnet', sid, "update");
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+# Create dhcp4_options insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_AINS AFTER INSERT ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("create", NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id);
+ END $$
+DELIMITER ;
+
+# Create dhcp4_options update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_AUPD AFTER UPDATE ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("update", NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id);
+ END $$
+DELIMITER ;
+
+# Create dhcp4_options delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_ADEL AFTER DELETE ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("delete", OLD.scope_id, OLD.option_id, OLD.dhcp4_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id);
+ END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Table parameter_data_type
+-- Reflects an enum used by Kea to define supported
+-- data types for the simple configuration parameters,
+-- e.g. global parameters used by DHCP servers.
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS parameter_data_type (
+ id TINYINT UNSIGNED NOT NULL PRIMARY KEY,
+ name VARCHAR(32) NOT NULL
+) ENGINE = InnoDB;
+
+START TRANSACTION;
+INSERT INTO parameter_data_type VALUES (0, 'integer');
+INSERT INTO parameter_data_type VALUES (1, 'real');
+INSERT INTO parameter_data_type VALUES (2, 'boolean');
+INSERT INTO parameter_data_type VALUES (4, 'string');
+COMMIT;
+
+ALTER TABLE dhcp4_global_parameter
+ ADD COLUMN parameter_type TINYINT UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp4_global_parameter
+ ADD CONSTRAINT fk_dhcp4_global_parameter_type FOREIGN KEY (parameter_type)
+ REFERENCES parameter_data_type (id);
+
+ALTER TABLE dhcp6_global_parameter
+ ADD COLUMN parameter_type TINYINT UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp6_global_parameter
+ ADD CONSTRAINT fk_dhcp6_global_parameter_type FOREIGN KEY (parameter_type)
+ REFERENCES parameter_data_type (id);
+
+
+-- Rename dhcp6_subnet_id column of dhcp6_pool and dhcp6_pd_pool
+
+ALTER TABLE dhcp6_pool
+ DROP FOREIGN KEY fk_dhcp6_pool_subnet_id;
+DROP INDEX fk_dhcp6_pool_subnet_id
+ ON dhcp6_pool;
+
+ALTER TABLE dhcp6_pd_pool
+ DROP FOREIGN KEY fk_dhcp6_pd_pool_subnet_id;
+DROP INDEX fk_dhcp6_pd_pool_subnet_id
+ ON dhcp6_pd_pool;
+
+ALTER TABLE dhcp6_pool
+ CHANGE dhcp6_subnet_id subnet_id INT(10) UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp6_pd_pool
+ CHANGE dhcp6_subnet_id subnet_id INT(10) UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp6_pool
+ ADD CONSTRAINT fk_dhcp6_pool_subnet_id
+ FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_pd_pool
+ ADD CONSTRAINT fk_dhcp6_pd_pool_subnet_id
+ FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- align dhcp6_shared_network_server indexes on dhcp4_shared_network_server
+
+ALTER TABLE dhcp6_shared_network_server
+ ADD PRIMARY KEY (shared_network_id, server_id);
+
+ALTER TABLE dhcp6_shared_network_server
+ DROP FOREIGN KEY fk_dhcp6_shared_network_server_shared_network_id;
+DROP INDEX fk_dhcp6_shared_network_server_shared_network_id
+ ON dhcp6_shared_network_server;
+ALTER TABLE dhcp6_shared_network_server
+ ADD CONSTRAINT fk_dhcp6_shared_network_server_shared_network_id
+ FOREIGN KEY (shared_network_id)
+ REFERENCES dhcp6_shared_network (id)
+ ON DELETE CASCADE ON UPDATE NO ACTION;
+
+-- Update dhcp4_subnet_server and dhcp6_subnet_server to allow update
+-- on the prefix too by setting the CASCADE action.
+
+ALTER TABLE dhcp4_subnet_server
+ DROP FOREIGN KEY fk_dhcp4_subnet_server_subnet_id;
+ALTER TABLE dhcp4_subnet_server
+ ADD CONSTRAINT fk_dhcp4_subnet_server_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp4_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_subnet_server
+ DROP FOREIGN KEY fk_dhcp6_subnet_server_subnet_id;
+ALTER TABLE dhcp6_subnet_server
+ ADD CONSTRAINT fk_dhcp6_subnet_server_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- -----------------------------------------------------
+-- Table dhcp6_audit_revision
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_audit_revision (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ modification_ts TIMESTAMP NOT NULL,
+ log_message TEXT,
+ server_id BIGINT(10) UNSIGNED,
+ PRIMARY KEY (id),
+ KEY key_dhcp6_audit_revision_by_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------
+-- Drop columns from the dhcp6_audit table which now
+-- belong to the dhcp6_audit_revision.
+-- -----------------------------------------------------
+ALTER TABLE dhcp6_audit
+ DROP COLUMN modification_ts,
+ DROP COLUMN log_message;
+
+-- -----------------------------------------------------
+-- Add column revision_id and the foreign key with a
+-- reference to the dhcp6_audit_revision table.
+-- -----------------------------------------------------
+ALTER TABLE dhcp6_audit
+ ADD COLUMN revision_id BIGINT(20) UNSIGNED NOT NULL;
+
+ALTER TABLE dhcp6_audit
+ ADD CONSTRAINT fk_dhcp6_audit_revision FOREIGN KEY (revision_id)
+ REFERENCES dhcp6_audit_revision (id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp6_audit_revision table and sets appropriate session
+-- variables to be used while creating the audit entries
+-- by triggers. This procedure should be called at the
+-- beginning of a transaction which modifies configuration
+-- data in the database, e.g. when new subnet is added.
+--
+-- Parameters:
+-- - audit_ts timestamp to be associated with the audit
+-- revision.
+-- - server_tag is used to retrieve the server_id which
+-- associates the changes applied with the particular
+-- server or all servers.
+-- - audit_log_message is a log message associates with
+-- the audit revision.
+-- - cascade_transaction is assigned to a session
+-- variable which is used in some triggers to determine
+-- if the audit entry should be created for them or
+-- not. Specifically, this is used when DHCP options
+-- are inserted, updated or deleted. If such modification
+-- is a part of the larger change (e.g. change in the
+-- subnet the options belong to) the dedicated audit
+-- entry for options must not be created. On the other
+-- hand, if the global option is being added, the
+-- audit entry for the option must be created because
+-- it is the sole object modified in that case.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createAuditRevisionDHCP6;
+DELIMITER $$
+CREATE PROCEDURE createAuditRevisionDHCP6(IN audit_ts TIMESTAMP,
+ IN server_tag VARCHAR(256),
+ IN audit_log_message TEXT,
+ IN cascade_transaction TINYINT(1))
+BEGIN
+ DECLARE srv_id BIGINT(20);
+ IF @disable_audit IS NULL OR @disable_audit = 0 THEN
+ SELECT id INTO srv_id FROM dhcp6_server WHERE tag = server_tag;
+ INSERT INTO dhcp6_audit_revision (modification_ts, server_id, log_message)
+ VALUES (audit_ts, srv_id, audit_log_message);
+ SET @audit_revision_id = LAST_INSERT_ID();
+ SET @cascade_transaction = cascade_transaction;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp6_audit table. It should be called from the
+-- triggers of the tables where the config modifications
+-- are applied. The @audit_revision_id variable contains
+-- the revision id to be placed in the audit entries.
+--
+-- The following parameters are passed to this procedure:
+-- - object_type_val: name of the table to be associated
+-- with the applied changes.
+-- - object_id_val: identifier of the modified object in
+-- that table.
+-- - modification_type_val: string value indicating the
+-- type of the change, i.e. "create", "update" or
+-- "delete".
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- ----------------------------------------------------
+DROP PROCEDURE IF EXISTS createAuditEntryDHCP6;
+DELIMITER $$
+CREATE PROCEDURE createAuditEntryDHCP6(IN object_type_val VARCHAR(256),
+ IN object_id_val BIGINT(20) UNSIGNED,
+ IN modification_type_val VARCHAR(32))
+BEGIN
+ IF @disable_audit IS NULL OR @disable_audit = 0 THEN
+ INSERT INTO dhcp6_audit (object_type, object_id, modification_type, revision_id)
+ VALUES (object_type_val, object_id_val, \
+ (SELECT id FROM modification WHERE modification_type = modification_type_val), \
+ @audit_revision_id);
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Triggers used to create entries in the audit
+-- tables upon insertion, update or deletion of the
+-- configuration entries.
+-- -----------------------------------------------------
+
+# Create dhcp6_global_parameter insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_global_parameter_AINS AFTER INSERT ON dhcp6_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_global_parameter', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_global_parameter update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_global_parameter_AUPD AFTER UPDATE ON dhcp6_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_global_parameter', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_global_parameter delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_global_parameter_ADEL AFTER DELETE ON dhcp6_global_parameter
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_global_parameter', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_subnet insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_subnet_AINS AFTER INSERT ON dhcp6_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_subnet', NEW.subnet_id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_subnet update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_subnet_AUPD AFTER UPDATE ON dhcp6_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_subnet', NEW.subnet_id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_subnet delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_subnet_ADEL AFTER DELETE ON dhcp6_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_subnet', OLD.subnet_id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_shared_network insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_shared_network_AINS AFTER INSERT ON dhcp6_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_shared_network update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_shared_network_AUPD AFTER UPDATE ON dhcp6_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_shared_network delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_shared_network_ADEL AFTER DELETE ON dhcp6_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_option_def insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_option_def_AINS AFTER INSERT ON dhcp6_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_option_def', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_option_def update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_option_def_AUPD AFTER UPDATE ON dhcp6_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_option_def', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_option_def delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_option_def_ADEL AFTER DELETE ON dhcp6_option_def
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_option_def', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates an audit entry for a
+-- DHCPv6 option. Depending on the scope of the option
+-- the audit entry can be created for various levels
+-- of configuration hierarchy. If this is a global
+-- option the audit entry is created for this option
+-- for CREATE, UPDATE or DELETE. If the option is being
+-- added for an owning option, e.g. for a subnet, the
+-- audit entry is created as an UPDATE to this object.
+-- From the Kea perspective such option addition will
+-- be seen as a subnet update and the server will fetch
+-- the whole subnet and merge it into its configuration.
+-- The audit entry is not created if it was already
+-- created as part of the current transaction.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP6;
+DELIMITER $$
+CREATE PROCEDURE createOptionAuditDHCP6(IN modification_type VARCHAR(32),
+ IN scope_id TINYINT(3) UNSIGNED,
+ IN option_id BIGINT(20) UNSIGNED,
+ IN subnet_id INT(10) UNSIGNED,
+ IN host_id INT(10) UNSIGNED,
+ IN network_name VARCHAR(128),
+ IN pool_id BIGINT(20),
+ IN pd_pool_id BIGINT(20))
+BEGIN
+ # These variables will hold shared network id and subnet id that
+ # we will select.
+ DECLARE snid VARCHAR(128);
+ DECLARE sid INT(10) UNSIGNED;
+
+ # Cascade transaction flag is set to 1 to prevent creation of
+ # the audit entries for the options when the options are
+ # created as part of the parent object creation or update.
+ # For example: when the option is added as part of the subnet
+ # addition, the cascade transaction flag is equal to 1. If
+ # the option is added into the existing subnet the cascade
+ # transaction is equal to 0. Note that depending on the option
+ # scope the audit entry will contain the object_type value
+ # of the parent object to cause the server to replace the
+ # entire subnet. The only case when the object_type will be
+ # set to 'dhcp6_options' is when a global option is added.
+ # Global options do not have the owner.
+ IF @cascade_transaction IS NULL OR @cascade_transaction = 0 THEN
+ # todo: host manager hasn't been updated to use audit
+ # mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ # If a global option is added or modified, create audit
+ # entry for the 'dhcp6_options' table.
+ CALL createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ # If subnet specific option is added or modified, create
+ # audit entry for the entire subnet, which indicates that
+ # it should be treated as the subnet update.
+ CALL createAuditEntryDHCP6('dhcp6_subnet', subnet_id, "update");
+ ELSEIF scope_id = 4 THEN
+ # If shared network specific option is added or modified,
+ # create audit entry for the shared network which
+ # indicates that it should be treated as the shared
+ # network update.
+ SELECT id INTO snid FROM dhcp6_shared_network WHERE name = network_name LIMIT 1;
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', snid, "update");
+ ELSEIF scope_id = 5 THEN
+ # If pool specific option is added or modified, create
+ # audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ CALL createAuditEntryDHCP6('dhcp6_subnet', sid, "update");
+ ELSEIF scope_id = 6 THEN
+ # If pd pool specific option is added or modified, create
+ # audit entry for the subnet which this pd pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pd_pool_id;
+ CALL createAuditEntryDHCP6('dhcp6_subnet', sid, "update");
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+# Create dhcp6_options insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_AINS AFTER INSERT ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("create", NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id, NEW.pd_pool_id);
+ END $$
+DELIMITER ;
+
+# Create dhcp6_options update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_AUPD AFTER UPDATE ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("update", NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id, NEW.pd_pool_id);
+ END $$
+DELIMITER ;
+
+# Create dhcp6_options delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_ADEL AFTER DELETE ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("delete", OLD.scope_id, OLD.option_id, OLD.dhcp6_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id, OLD.pd_pool_id);
+ END $$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '8', minor = '0';
+
+# This line concludes database upgrade to version 8.0.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_008.0_to_008.1.sh.in b/src/share/database/scripts/mysql/upgrade_008.0_to_008.1.sh.in
new file mode 100644
index 0000000..f2c3e49
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_008.0_to_008.1.sh.in
@@ -0,0 +1,123 @@
+#!/bin/sh
+
+# Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "8.0" ]; then
+ printf 'This script upgrades 8.0 to 8.1. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Add lifetime bounds
+ALTER TABLE dhcp4_shared_network
+ ADD COLUMN min_valid_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_valid_lifetime INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp4_subnet
+ ADD COLUMN min_valid_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_valid_lifetime INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ ADD COLUMN min_preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN min_valid_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_valid_lifetime INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ ADD COLUMN min_preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN min_valid_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_valid_lifetime INT(10) DEFAULT NULL;
+
+# Create dhcp4_server insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_server_AINS AFTER INSERT ON dhcp4_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_server', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_server update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_server_AUPD AFTER UPDATE ON dhcp4_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_server', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp4_server delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp4_server_ADEL AFTER DELETE ON dhcp4_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_server', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_server insert trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_server_AINS AFTER INSERT ON dhcp6_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_server', NEW.id, "create");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_server update trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_server_AUPD AFTER UPDATE ON dhcp6_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_server', NEW.id, "update");
+ END $$
+DELIMITER ;
+
+# Create dhcp6_server delete trigger
+DELIMITER $$
+CREATE TRIGGER dhcp6_server_ADEL AFTER DELETE ON dhcp6_server
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_server', OLD.id, "delete");
+ END $$
+DELIMITER ;
+
+# Put the auth key in hexadecimal (double size but far more user friendly).
+ALTER TABLE hosts
+ MODIFY COLUMN auth_key VARCHAR(32) NULL;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '8', minor = '1';
+
+# This line concludes database upgrade to version 8.1.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_008.1_to_008.2.sh.in b/src/share/database/scripts/mysql/upgrade_008.1_to_008.2.sh.in
new file mode 100644
index 0000000..4ad298e
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_008.1_to_008.2.sh.in
@@ -0,0 +1,500 @@
+#!/bin/sh
+
+# Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "8.1" ]; then
+ printf 'This script upgrades 8.1 to 8.2. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+# Get the schema name from database argument. We need this to
+# query information_schema for the right database.
+for arg in "${@}"
+do
+ if ! printf '%s' "${arg}" | grep -Eq '^\-\-'
+ then
+ schema="$arg"
+ break
+ fi
+done
+
+# Make sure we can id the schema
+if [ -z "$schema" ]
+then
+ printf "Could not find database schema name in cmd line args: %s\n" "${*}"
+ exit 255
+fi
+
+# Save the command line args, as we use these later change_column function.
+
+# Function to rename a column in a table.
+change_column() {
+ local schema="${1-}"; shift
+ local table="${1-}"; shift
+ local ocolumn="${1-}"; shift
+ local ncolumn="${1-}"; shift
+
+ # First let's find out if the column name in the table actually needs updating.
+ sql="select count(column_name) from information_schema.columns where table_schema='$schema' and table_name = '$table' and column_name = '$ocolumn'"
+ if ! count=$(mysql -N -B "${@}" -e "${sql}")
+ then
+ printf 'change_column: schema query failed [%s]\n' "${sql}"
+ exit 255
+ fi
+
+ # If we found a match record, the column needs to be renamed
+ if [ "$count" -eq 1 ]
+ then
+ sql="ALTER TABLE $table CHANGE COLUMN $ocolumn $ncolumn"
+ if ! mysql -N -B "${@}" -e "${sql}"
+ then
+ printf 'change_column: alter query failed [%s]\n' "${sql}"
+ exit 255
+ fi
+ else
+ printf '%s column is already correct\n' "${table}"
+ fi
+}
+
+mysql "$@" <<EOF
+
+# Drop existing trigger on the dhcp4_shared_network table.
+DROP TRIGGER dhcp4_shared_network_ADEL;
+
+# Create new trigger which will delete options associated with the shared
+# network.
+DELIMITER $$
+CREATE TRIGGER dhcp4_shared_network_BDEL BEFORE DELETE ON dhcp4_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, "delete");
+ DELETE FROM dhcp4_options WHERE shared_network_name = OLD.name;
+ END $$
+DELIMITER ;
+
+# Drop existing trigger on the dhcp4_subnet table.
+DROP TRIGGER dhcp4_subnet_ADEL;
+
+# Create new trigger which will delete pools associated with the subnet and
+# the options associated with the subnet.
+DELIMITER $$
+CREATE TRIGGER dhcp4_subnet_BDEL BEFORE DELETE ON dhcp4_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_subnet', OLD.subnet_id, "delete");
+ DELETE FROM dhcp4_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp4_options WHERE dhcp4_subnet_id = OLD.subnet_id;
+ END $$
+DELIMITER ;
+
+# Do not perform cascade deletion of the data in the dhcp4_pool because
+# the cascade deletion does not execute triggers associated with the table.
+# Instead we are going to use triggers on the dhcp4_subnet table.
+ALTER TABLE dhcp4_pool
+ DROP FOREIGN KEY fk_dhcp4_pool_subnet_id;
+
+ALTER TABLE dhcp4_pool
+ ADD CONSTRAINT fk_dhcp4_pool_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp4_subnet (subnet_id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+# Drop existing trigger on the dhcp6_shared_network table.
+DROP TRIGGER dhcp6_shared_network_ADEL;
+
+# Create new trigger which will delete options associated with the shared
+# network.
+DELIMITER $$
+CREATE TRIGGER dhcp6_shared_network_BDEL BEFORE DELETE ON dhcp6_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, "delete");
+ DELETE FROM dhcp6_options WHERE shared_network_name = OLD.name;
+ END $$
+DELIMITER ;
+
+# Drop existing trigger on the dhcp6_subnet table.
+DROP TRIGGER dhcp6_subnet_ADEL;
+
+# Create new trigger which will delete pools associated with the subnet and
+# the options associated with the subnet.
+DELIMITER $$
+CREATE TRIGGER dhcp6_subnet_BDEL BEFORE DELETE ON dhcp6_subnet
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_subnet', OLD.subnet_id, "delete");
+ DELETE FROM dhcp6_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp6_pd_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp6_options WHERE dhcp6_subnet_id = OLD.subnet_id;
+ END $$
+DELIMITER ;
+
+# Do not perform cascade deletion of the data in the dhcp6_pool and dhcp6_pd_pool
+# because the cascaded deletion does not execute triggers associated with the table.
+# Instead we are going to use triggers on the dhcp6_subnet table.
+ALTER TABLE dhcp6_pool
+ DROP FOREIGN KEY fk_dhcp6_pool_subnet_id;
+
+ALTER TABLE dhcp6_pd_pool
+ DROP FOREIGN KEY fk_dhcp6_pd_pool_subnet_id;
+
+ALTER TABLE dhcp6_pool
+ ADD CONSTRAINT fk_dhcp6_pool_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_pd_pool
+ ADD CONSTRAINT fk_dhcp6_pd_pool_subnet_id FOREIGN KEY (subnet_id)
+ REFERENCES dhcp6_subnet (subnet_id)
+ ON DELETE NO ACTION ON UPDATE CASCADE;
+
+# Create trigger which removes pool specific options upon removal of
+# the pool.
+DELIMITER $$
+CREATE TRIGGER dhcp6_pd_pool_BDEL BEFORE DELETE ON dhcp6_pd_pool FOR EACH ROW
+BEGIN
+DELETE FROM dhcp6_options WHERE scope_id = 6 AND pd_pool_id = OLD.id;
+END
+$$
+DELIMITER ;
+
+# Add missing columns in pools.
+ALTER TABLE dhcp4_pool
+ ADD COLUMN client_class VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN require_client_classes LONGTEXT,
+ ADD COLUMN user_context LONGTEXT;
+
+ALTER TABLE dhcp6_pd_pool
+ ADD COLUMN excluded_prefix VARCHAR(45) DEFAULT NULL,
+ ADD COLUMN excluded_prefix_length TINYINT(3) NOT NULL,
+ ADD COLUMN client_class VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN require_client_classes LONGTEXT,
+ ADD COLUMN user_context LONGTEXT;
+
+ALTER TABLE dhcp6_pool
+ ADD COLUMN client_class VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN require_client_classes LONGTEXT,
+ ADD COLUMN user_context LONGTEXT;
+
+-- -----------------------------------------------------
+--
+-- New version of the createOptionAuditDHCP4 stored
+-- procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - modification_ts: modification timestamp of the
+-- option.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP4;
+DELIMITER $$
+CREATE PROCEDURE createOptionAuditDHCP4(IN modification_type VARCHAR(32),
+ IN scope_id TINYINT(3) UNSIGNED,
+ IN option_id BIGINT(20) UNSIGNED,
+ IN subnet_id INT(10) UNSIGNED,
+ IN host_id INT(10) UNSIGNED,
+ IN network_name VARCHAR(128),
+ IN pool_id BIGINT(20),
+ IN modification_ts TIMESTAMP)
+BEGIN
+ # These variables will hold shared network id and subnet id that
+ # we will select.
+ DECLARE snid VARCHAR(128);
+ DECLARE sid INT(10) UNSIGNED;
+
+ # Cascade transaction flag is set to 1 to prevent creation of
+ # the audit entries for the options when the options are
+ # created as part of the parent object creation or update.
+ # For example: when the option is added as part of the subnet
+ # addition, the cascade transaction flag is equal to 1. If
+ # the option is added into the existing subnet the cascade
+ # transaction is equal to 0. Note that depending on the option
+ # scope the audit entry will contain the object_type value
+ # of the parent object to cause the server to replace the
+ # entire subnet. The only case when the object_type will be
+ # set to 'dhcp4_options' is when a global option is added.
+ # Global options do not have the owner.
+ IF @cascade_transaction IS NULL OR @cascade_transaction = 0 THEN
+ # todo: host manager hasn't been updated to use audit
+ # mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ # If a global option is added or modified, create audit
+ # entry for the 'dhcp4_options' table.
+ CALL createAuditEntryDHCP4('dhcp4_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ # If subnet specific option is added or modified, update
+ # the modification timestamp of this subnet to allow the
+ # servers to refresh the subnet information. This will
+ # also result in creating an audit entry for this subnet.
+ UPDATE dhcp4_subnet AS s SET s.modification_ts = modification_ts
+ WHERE s.subnet_id = subnet_id;
+ ELSEIF scope_id = 4 THEN
+ # If shared network specific option is added or modified,
+ # update the modification timestamp of this shared network
+ # to allow the servers to refresh the shared network
+ # information. This will also result in creating an
+ # audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp4_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp4_shared_network AS n SET n.modification_ts = modification_ts
+ WHERE n.id = snid;
+ ELSEIF scope_id = 5 THEN
+ # If pool specific option is added or modified, update
+ # the modification timestamp of the owning subnet.
+ SELECT dhcp4_pool.subnet_id INTO sid FROM dhcp4_pool WHERE id = pool_id;
+ UPDATE dhcp4_subnet AS s SET s.modification_ts = modification_ts
+ WHERE s.subnet_id = sid;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+# Recreate dhcp4_options_AINS trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP4.
+DROP TRIGGER IF EXISTS dhcp4_options_AINS;
+
+# This trigger is executed after inserting a DHCPv4 option into the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_AINS AFTER INSERT ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("create", NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.modification_ts);
+ END $$
+DELIMITER ;
+
+# Recreate dhcp4_options_AUPD trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP4.
+DROP TRIGGER IF EXISTS dhcp4_options_AUPD;
+
+# This trigger is executed after updating a DHCPv4 option in the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_AUPD AFTER UPDATE ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("update", NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.modification_ts);
+ END $$
+DELIMITER ;
+
+# Recreate dhcp4_options_ADEL trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP4.
+DROP TRIGGER IF EXISTS dhcp4_options_ADEL;
+
+# This trigger is executed after deleting a DHCPv4 option in the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp4_options_ADEL AFTER DELETE ON dhcp4_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP4("delete", OLD.scope_id, OLD.option_id, OLD.dhcp4_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id,
+ NOW());
+ END $$
+DELIMITER ;
+
+
+-- -----------------------------------------------------
+--
+-- New version of the createOptionAuditDHCP4 stored
+-- procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- - modification_ts: modification timestamp of the
+-- option.
+-- -----------------------------------------------------
+DROP PROCEDURE IF EXISTS createOptionAuditDHCP6;
+DELIMITER $$
+CREATE PROCEDURE createOptionAuditDHCP6(IN modification_type VARCHAR(32),
+ IN scope_id TINYINT(3) UNSIGNED,
+ IN option_id BIGINT(20) UNSIGNED,
+ IN subnet_id INT(10) UNSIGNED,
+ IN host_id INT(10) UNSIGNED,
+ IN network_name VARCHAR(128),
+ IN pool_id BIGINT(20),
+ IN pd_pool_id BIGINT(20),
+ IN modification_ts TIMESTAMP)
+BEGIN
+ # These variables will hold shared network id and subnet id that
+ # we will select.
+ DECLARE snid VARCHAR(128);
+ DECLARE sid INT(10) UNSIGNED;
+
+ # Cascade transaction flag is set to 1 to prevent creation of
+ # the audit entries for the options when the options are
+ # created as part of the parent object creation or update.
+ # For example: when the option is added as part of the subnet
+ # addition, the cascade transaction flag is equal to 1. If
+ # the option is added into the existing subnet the cascade
+ # transaction is equal to 0. Note that depending on the option
+ # scope the audit entry will contain the object_type value
+ # of the parent object to cause the server to replace the
+ # entire subnet. The only case when the object_type will be
+ # set to 'dhcp6_options' is when a global option is added.
+ # Global options do not have the owner.
+ IF @cascade_transaction IS NULL OR @cascade_transaction = 0 THEN
+ # todo: host manager hasn't been updated to use audit
+ # mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ # If a global option is added or modified, create audit
+ # entry for the 'dhcp6_options' table.
+ CALL createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ # If subnet specific option is added or modified, update
+ # the modification timestamp of this subnet to allow the
+ # servers to refresh the subnet information. This will
+ # also result in creating an audit entry for this subnet.
+ UPDATE dhcp6_subnet AS s SET s.modification_ts = modification_ts
+ WHERE s.subnet_id = subnet_id;
+ ELSEIF scope_id = 4 THEN
+ # If shared network specific option is added or modified,
+ # update the modification timestamp of this shared network
+ # to allow the servers to refresh the shared network
+ # information. This will also result in creating an
+ # audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp6_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp6_shared_network AS n SET n.modification_ts = modification_ts
+ WHERE n.id = snid;
+ ELSEIF scope_id = 5 THEN
+ # If pool specific option is added or modified, update
+ # the modification timestamp of the owning subnet.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet AS s SET s.modification_ts = modification_ts
+ WHERE s.subnet_id = sid;
+ ELSEIF scope_id = 6 THEN
+ # If pd pool specific option is added or modified, create
+ # audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pd_pool_id;
+ UPDATE dhcp6_subnet AS s SET s.modification_ts = modification_ts
+ WHERE s.subnet_id = sid;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+# Recreate dhcp6_options_AINS trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP6.
+DROP TRIGGER IF EXISTS dhcp6_options_AINS;
+
+# This trigger is executed after inserting a DHCPv6 option into the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_AINS AFTER INSERT ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("create", NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.pd_pool_id, NEW.modification_ts);
+ END $$
+DELIMITER ;
+
+# Recreate dhcp6_options_AUPD trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP6.
+DROP TRIGGER IF EXISTS dhcp6_options_AUPD;
+
+# This trigger is executed after updating a DHCPv6 option in the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_AUPD AFTER UPDATE ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("update", NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.pd_pool_id, NEW.modification_ts);
+ END $$
+DELIMITER ;
+
+# Recreate dhcp6_options_ADEL trigger to pass timestamp to the updated
+# version of the createOptionAuditDHCP6.
+DROP TRIGGER IF EXISTS dhcp6_options_ADEL;
+
+# This trigger is executed after deleting a DHCPv6 option in the
+# database. It creates appropriate audit entry for this option or
+# a parent object owning this option.
+DELIMITER $$
+CREATE TRIGGER dhcp6_options_ADEL AFTER DELETE ON dhcp6_options
+ FOR EACH ROW
+ BEGIN
+ CALL createOptionAuditDHCP6("delete", OLD.scope_id, OLD.option_id, OLD.dhcp6_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id,
+ OLD.pd_pool_id, NOW());
+ END $$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '8', minor = '2';
+
+# This line concludes database upgrade to version 8.2.
+
+EOF
+
+# We need to rename the columns in the option def tables because "array" is
+# a MySQL keyword as of 8.0.17
+change_column "${schema}" dhcp4_option_def array "is_array TINYINT(1) NOT NULL" "${@}"
+change_column "${schema}" dhcp6_option_def array "is_array TINYINT(1) NOT NULL" "${@}"
diff --git a/src/share/database/scripts/mysql/upgrade_008.2_to_009.0.sh.in b/src/share/database/scripts/mysql/upgrade_008.2_to_009.0.sh.in
new file mode 100644
index 0000000..fdbf17d
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_008.2_to_009.0.sh.in
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+# Copyright (C) 2019-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "8.2" ]; then
+ printf 'This script upgrades 8.2 to 9.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Create hostname index for host reservations
+CREATE INDEX hosts_by_hostname ON hosts (hostname);
+
+# Move to lower case hostnames in lease4 table.
+UPDATE lease4 SET hostname = LOWER(hostname)
+WHERE BINARY LOWER(hostname) != hostname;
+
+# Move to lower case hostnames in lease6 table.
+UPDATE lease6 SET hostname = LOWER(hostname)
+WHERE BINARY LOWER(hostname) != hostname;
+
+# Create hostname index for lease4
+CREATE INDEX lease4_by_hostname ON lease4 (hostname);
+
+# Create hostname index for lease6
+CREATE INDEX lease6_by_hostname ON lease6 (hostname);
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '0';
+
+# This line concludes database upgrade to version 9.0.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_009.0_to_009.1.sh.in b/src/share/database/scripts/mysql/upgrade_009.0_to_009.1.sh.in
new file mode 100644
index 0000000..473a18f
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_009.0_to_009.1.sh.in
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+# Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "9.0" ]; then
+ printf 'This script upgrades 9.0 to 9.1. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Add new DDNS related columns to shared networks and subnets
+ALTER TABLE dhcp4_shared_network
+ ADD COLUMN ddns_send_updates TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_no_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_client_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_replace_client_name TINYINT(3) DEFAULT NULL,
+ ADD COLUMN ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ADD COLUMN ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ ADD COLUMN ddns_send_updates TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_no_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_client_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_replace_client_name TINYINT(3) DEFAULT NULL,
+ ADD COLUMN ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ADD COLUMN ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL;
+
+ALTER TABLE dhcp4_subnet
+ ADD COLUMN ddns_send_updates TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_no_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_client_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_replace_client_name TINYINT(3) DEFAULT NULL,
+ ADD COLUMN ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ADD COLUMN ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ ADD COLUMN ddns_send_updates TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_no_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_override_client_update TINYINT(1) DEFAULT NULL,
+ ADD COLUMN ddns_replace_client_name TINYINT(3) DEFAULT NULL,
+ ADD COLUMN ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ADD COLUMN ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '1';
+
+# This line concludes database upgrade to version 9.1.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_009.1_to_009.2.sh.in b/src/share/database/scripts/mysql/upgrade_009.1_to_009.2.sh.in
new file mode 100644
index 0000000..3b8cd34
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_009.1_to_009.2.sh.in
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+# Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "9.1" ]; then
+ printf 'This script upgrades 9.1 to 9.2. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Add missing indexes (foreign keys) to the dhcp4_options table.
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_shared_network
+ FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network(name)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_subnet
+ FOREIGN KEY (dhcp4_subnet_id)
+ REFERENCES dhcp4_subnet(subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_pool
+ FOREIGN KEY (pool_id)
+ REFERENCES dhcp4_pool(id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+# Add missing indexes (foreign keys) to the dhcp6_options table.
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_shared_network
+ FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network(name)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_subnet
+ FOREIGN KEY (dhcp6_subnet_id)
+ REFERENCES dhcp6_subnet(subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_pool
+ FOREIGN KEY (pool_id)
+ REFERENCES dhcp6_pool(id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_pd_pool
+ FOREIGN KEY (pd_pool_id)
+ REFERENCES dhcp6_pd_pool(id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '2';
+
+# This line concludes database upgrade to version 9.2.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_009.2_to_009.3.sh.in b/src/share/database/scripts/mysql/upgrade_009.2_to_009.3.sh.in
new file mode 100644
index 0000000..76e8608
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_009.2_to_009.3.sh.in
@@ -0,0 +1,140 @@
+#!/bin/sh
+
+# Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "9.2" ]; then
+ printf 'This script upgrades 9.2 to 9.3. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Fix stat_lease4_update trigger
+DROP TRIGGER stat_lease4_update;
+
+DELIMITER $$
+CREATE TRIGGER stat_lease4_update AFTER UPDATE ON lease4
+ FOR EACH ROW
+ BEGIN
+ IF OLD.subnet_id != NEW.subnet_id OR OLD.state != NEW.state THEN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the old state count if record exists
+ UPDATE lease4_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND state = OLD.state;
+ END IF;
+
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Increment the new state count if record exists
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ # Insert new state record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat VALUES (NEW.subnet_id, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Fix stat_lease4_delete trigger
+DROP TRIGGER stat_lease4_delete;
+
+DELIMITER $$
+CREATE TRIGGER stat_lease4_delete AFTER DELETE ON lease4
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the state count if record exists
+ UPDATE lease4_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND OLD.state = state;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Fix stat_lease6_update trigger
+DROP TRIGGER stat_lease6_update;
+
+DELIMITER $$
+CREATE TRIGGER stat_lease6_update AFTER UPDATE ON lease6
+ FOR EACH ROW
+ BEGIN
+ IF OLD.subnet_id != NEW.subnet_id OR
+ OLD.lease_type != NEW.lease_type OR
+ OLD.state != NEW.state THEN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the old state count if record exists
+ UPDATE lease6_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+
+ IF NEW.state = 0 OR NEW.state = 1 THEN
+ # Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ # Insert new state record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat
+ VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Fix stat_lease6_delete trigger
+DROP TRIGGER stat_lease6_delete;
+
+DELIMITER $$
+CREATE TRIGGER stat_lease6_delete AFTER DELETE ON lease6
+ FOR EACH ROW
+ BEGIN
+ IF OLD.state = 0 OR OLD.state = 1 THEN
+ # Decrement the state count if record exists
+ UPDATE lease6_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+ END $$
+DELIMITER ;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '3';
+
+# This line concludes database upgrade to version 9.3.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_009.3_to_009.4.sh.in b/src/share/database/scripts/mysql/upgrade_009.3_to_009.4.sh.in
new file mode 100644
index 0000000..69b3f65
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_009.3_to_009.4.sh.in
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+# Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "9.3" ]; then
+ printf 'This script upgrades 9.3 to 9.4. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Starting from this version we allow specifying multiple IP reservations
+# for the same address in certain DHCP configurations. The server may check
+# uniqueness of the IP addresses on its own. This is no longer checked at
+# the database level to facilitate the use cases when a single host may
+# get the same reserved IP address via different interfaces.
+
+# Replace the unique index with non-unique index so the queries for
+# hosts by IPv4 address are still efficient.
+DROP INDEX key_dhcp4_ipv4_address_subnet_id ON hosts;
+CREATE INDEX key_dhcp4_ipv4_address_subnet_id_identifier
+ ON hosts (ipv4_address ASC, dhcp4_subnet_id ASC);
+
+# Replace the unique index with non-unique index so the queries for
+# hosts by IPv6 address are still efficient.
+DROP INDEX key_dhcp6_address_prefix_len ON ipv6_reservations;
+CREATE INDEX key_dhcp6_address_prefix_len
+ ON ipv6_reservations (address ASC, prefix_len ASC);
+
+# Stop using a trigger to delete entries dependent on hosts table.
+# Use cascade action instead. This works better with complex delete
+# statements.
+DROP TRIGGER IF EXISTS host_BDEL;
+
+# Replace existing constraint to set cascade actions.
+ALTER TABLE ipv6_reservations DROP FOREIGN KEY fk_ipv6_reservations_Host;
+ALTER TABLE ipv6_reservations ADD CONSTRAINT fk_ipv6_reservations_Host
+ FOREIGN KEY (host_id)
+ REFERENCES hosts(host_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_Host
+ FOREIGN KEY (host_id)
+ REFERENCES hosts(host_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_Host
+ FOREIGN KEY (host_id)
+ REFERENCES hosts(host_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '4';
+
+# This line concludes database upgrade to version 9.4.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_009.4_to_009.5.sh.in b/src/share/database/scripts/mysql/upgrade_009.4_to_009.5.sh.in
new file mode 100644
index 0000000..ebf3117
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_009.4_to_009.5.sh.in
@@ -0,0 +1,170 @@
+#!/bin/sh
+
+# Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(mysql_version "$@")
+
+if [ "$VERSION" != "9.4" ]; then
+ printf 'This script upgrades 9.4 to 9.5. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+mysql "$@" <<EOF
+
+# Add new reservations flags.
+ALTER TABLE dhcp4_subnet
+ ADD COLUMN reservations_global BOOL DEFAULT NULL,
+ ADD COLUMN reservations_in_subnet BOOL DEFAULT NULL,
+ ADD COLUMN reservations_out_of_pool BOOL DEFAULT NULL;
+
+ALTER TABLE dhcp4_shared_network
+ ADD COLUMN reservations_global BOOL DEFAULT NULL,
+ ADD COLUMN reservations_in_subnet BOOL DEFAULT NULL,
+ ADD COLUMN reservations_out_of_pool BOOL DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ ADD COLUMN reservations_global BOOL DEFAULT NULL,
+ ADD COLUMN reservations_in_subnet BOOL DEFAULT NULL,
+ ADD COLUMN reservations_out_of_pool BOOL DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ ADD COLUMN reservations_global BOOL DEFAULT NULL,
+ ADD COLUMN reservations_in_subnet BOOL DEFAULT NULL,
+ ADD COLUMN reservations_out_of_pool BOOL DEFAULT NULL;
+
+# Disable audit in this session
+SET @disable_audit = 1;
+
+# Translate reservation_mode to new flags.
+# 0 is DISABLED
+# 1 is OUT_OF_POOL
+# 2 is GLOBAL
+# 3 is ALL
+UPDATE dhcp4_subnet
+ SET reservations_global = FALSE,
+ reservations_in_subnet = FALSE
+ WHERE reservation_mode = 0;
+
+UPDATE dhcp4_subnet
+ SET reservations_global = FALSE,
+ reservations_in_subnet = TRUE,
+ reservations_out_of_pool = TRUE
+ WHERE reservation_mode = 1;
+
+UPDATE dhcp4_subnet
+ SET reservations_global = TRUE,
+ reservations_in_subnet = FALSE
+ WHERE reservation_mode = 2;
+
+UPDATE dhcp4_subnet
+ SET reservations_global = FALSE,
+ reservations_in_subnet = TRUE,
+ reservations_out_of_pool = FALSE
+ WHERE reservation_mode = 3;
+
+UPDATE dhcp4_shared_network
+ SET reservations_global = FALSE,
+ reservations_in_subnet = FALSE
+ WHERE reservation_mode = 0;
+
+UPDATE dhcp4_shared_network
+ SET reservations_global = FALSE,
+ reservations_in_subnet = TRUE,
+ reservations_out_of_pool = TRUE
+ WHERE reservation_mode = 1;
+
+UPDATE dhcp4_shared_network
+ SET reservations_global = TRUE,
+ reservations_in_subnet = FALSE
+ WHERE reservation_mode = 2;
+
+UPDATE dhcp4_shared_network
+ SET reservations_global = FALSE,
+ reservations_in_subnet = TRUE,
+ reservations_out_of_pool = FALSE
+ WHERE reservation_mode = 3;
+
+UPDATE dhcp6_subnet
+ SET reservations_global = FALSE,
+ reservations_in_subnet = FALSE
+ WHERE reservation_mode = 0;
+
+UPDATE dhcp6_subnet
+ SET reservations_global = FALSE,
+ reservations_in_subnet = TRUE,
+ reservations_out_of_pool = TRUE
+ WHERE reservation_mode = 1;
+
+UPDATE dhcp6_subnet
+ SET reservations_global = TRUE,
+ reservations_in_subnet = FALSE
+ WHERE reservation_mode = 2;
+
+UPDATE dhcp6_subnet
+ SET reservations_global = FALSE,
+ reservations_in_subnet = TRUE,
+ reservations_out_of_pool = FALSE
+ WHERE reservation_mode = 3;
+
+UPDATE dhcp6_shared_network
+ SET reservations_global = FALSE,
+ reservations_in_subnet = FALSE
+ WHERE reservation_mode = 0;
+
+UPDATE dhcp6_shared_network
+ SET reservations_global = FALSE,
+ reservations_in_subnet = TRUE,
+ reservations_out_of_pool = TRUE
+ WHERE reservation_mode = 1;
+
+UPDATE dhcp6_shared_network
+ SET reservations_global = TRUE,
+ reservations_in_subnet = FALSE
+ WHERE reservation_mode = 2;
+
+UPDATE dhcp6_shared_network
+ SET reservations_global = FALSE,
+ reservations_in_subnet = TRUE,
+ reservations_out_of_pool = FALSE
+ WHERE reservation_mode = 3;
+
+# DROP reservation_mode
+ALTER TABLE dhcp4_subnet DROP COLUMN reservation_mode;
+ALTER TABLE dhcp4_shared_network DROP COLUMN reservation_mode;
+ALTER TABLE dhcp6_subnet DROP COLUMN reservation_mode;
+ALTER TABLE dhcp6_shared_network DROP COLUMN reservation_mode;
+
+# Enable audit in this session
+SET @disable_audit = 0;
+
+# Update the schema version number
+UPDATE schema_version
+SET version = '9', minor = '5';
+
+# This line concludes database upgrade to version 9.5.
+
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_009.5_to_009.6.sh.in b/src/share/database/scripts/mysql/upgrade_009.5_to_009.6.sh.in
new file mode 100644
index 0000000..717730b
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_009.5_to_009.6.sh.in
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+# Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+# Check version.
+version=$(mysql_version "${@}")
+if test "${version}" != "9.5"; then
+ printf 'This script upgrades 9.5 to 9.6. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${version}"
+ exit 0
+fi
+
+# Add column only if it doesn't exist to work around the 1.9.4 leak of
+# cache_threshold and cache_max_age column alters in subnet and shared network
+# tables in schema version 9.5.
+if ! mysql "${@}" -e 'SELECT cache_threshold FROM dhcp4_subnet LIMIT 1' &> /dev/null; then
+ mysql "${@}" <<EOF
+# Add new lease cache parameters.
+ALTER TABLE dhcp4_subnet
+ ADD COLUMN cache_threshold FLOAT DEFAULT NULL,
+ ADD COLUMN cache_max_age INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp4_shared_network
+ ADD COLUMN cache_threshold FLOAT DEFAULT NULL,
+ ADD COLUMN cache_max_age INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp6_subnet
+ ADD COLUMN cache_threshold FLOAT DEFAULT NULL,
+ ADD COLUMN cache_max_age INT(10) DEFAULT NULL;
+
+ALTER TABLE dhcp6_shared_network
+ ADD COLUMN cache_threshold FLOAT DEFAULT NULL,
+ ADD COLUMN cache_max_age INT(10) DEFAULT NULL;
+EOF
+fi
+
+mysql "${@}" <<EOF
+# Add an auto-increment ID as primary key to support Percona.
+ALTER TABLE logs
+ ADD id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY;
+
+# Update the schema version number
+UPDATE schema_version
+ SET version = '9', minor = '6';
+
+# This line concludes database upgrade to version 9.6.
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_009.6_to_010.0.sh.in b/src/share/database/scripts/mysql/upgrade_009.6_to_010.0.sh.in
new file mode 100644
index 0000000..1b01cad
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_009.6_to_010.0.sh.in
@@ -0,0 +1,1029 @@
+#!/bin/sh
+
+# Copyright (C) 2021-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+# Check version.
+version=$(mysql_version "${@}")
+if test "${version}" != "9.6"; then
+ printf 'This script upgrades 9.6 to 10.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${version}"
+ exit 0
+fi
+
+# Get the schema name from database argument. We need this to
+# query information_schema for the right database.
+for arg in "${@}"
+do
+ if ! printf '%s' "${arg}" | grep -Eq '^\-\-'
+ then
+ schema="$arg"
+ break
+ fi
+done
+
+# Make sure we have the schema.
+if [ -z "$schema" ]
+then
+ printf "Could not find database schema name in cmd line args: %s\n" "${*}"
+ exit 255
+fi
+
+shrink_tag_column() {
+ local schema="${1-}"; shift
+ local table="${1-}"; shift
+
+ # Check if the table already has a correct server tag length.
+ sql="SELECT CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='${schema}' AND TABLE_NAME='${table}' AND COLUMN_NAME='tag'"
+ if ! taglen=$(mysql -N -B "${@}" -e "${sql}")
+ then
+ printf 'shrink_tag_column: schema query failed [%s]\n' "${sql}"
+ exit 255
+ fi
+
+ # We aim for the length of 64 characters.
+ if [ "$taglen" -ne 64 ]
+ then
+ # Check if any of the tags are longer than 64 characters.
+ sql="SELECT COUNT(*) FROM $table WHERE CHAR_LENGTH(tag) > 64 LIMIT 1"
+ if ! longtag=$(mysql -N -B "${@}" -e "${sql}")
+ then
+ printf 'shrink_tag_column: select query failed [%s]\n' "${sql}"
+ exit 255
+ fi
+ # Report an error if there are any server tags exceeding 64 characters.
+ # A user should fix the tags and rerun this migration.
+ if [ "$longtag" -eq 1 ]
+ then
+ printf 'shrink_tag_column: failed to resize server tag column for table %s.\n' "${table}"
+ printf 'Ensure that no server tags are longer than 64 characters and rerun this migration.\n'
+ printf 'The remote-server4-set and remote-server6-set commands from the cb_cmds hooks\n'
+ printf 'library can be used to modify the tags.\n'
+ exit 255
+ fi
+ # If there are no long server tags we can safely alter the column.
+ sql="ALTER TABLE $table MODIFY COLUMN tag VARCHAR(64) NOT NULL"
+ if ! mysql -N -B "${@}" -e "${sql}"
+ then
+ printf 'shrink_tag_column: alter query failed [%s]\n' "${sql}"
+ exit 255
+ fi
+ fi
+}
+
+shrink_tag_column "${schema}" dhcp4_server "${@}"
+shrink_tag_column "${schema}" dhcp6_server "${@}"
+
+mysql "$@" <<EOF
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv4 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp4_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class (
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ test TEXT,
+ next_server INT UNSIGNED DEFAULT NULL,
+ server_hostname VARCHAR(128) DEFAULT NULL,
+ boot_file_name VARCHAR(512) DEFAULT NULL,
+ only_if_required TINYINT NOT NULL DEFAULT '0',
+ valid_lifetime INT DEFAULT NULL,
+ min_valid_lifetime INT DEFAULT NULL,
+ max_valid_lifetime INT DEFAULT NULL,
+ depend_on_known_directly TINYINT NOT NULL DEFAULT '0',
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY id_UNIQUE (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp4_client_class_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp4_client_class table has a corresponding row
+-- in the dhcp4_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp4_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp4_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_indirectly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp4_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_order (
+ class_id BIGINT UNSIGNED NOT NULL,
+ order_index BIGINT UNSIGNED NOT NULL,
+ depend_on_known_indirectly TINYINT NOT NULL DEFAULT '0',
+ PRIMARY KEY (class_id),
+ KEY key_dhcp4_client_class_order_index (order_index),
+ CONSTRAINT fk_dhcp4_client_class_order_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp4_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp4_client_class_ADEL;
+DROP PROCEDURE IF EXISTS setClientClass4Order;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE setClientClass4Order(IN id BIGINT UNSIGNED,
+ IN follow_class_name VARCHAR(128),
+ IN old_follow_class_name VARCHAR(128))
+proc_label:BEGIN
+ -- This variable will be optionally set if the follow_class_name
+ -- column value is specified.
+ DECLARE follow_class_index BIGINT UNSIGNED;
+ DECLARE msg TEXT;
+
+ -- Remember currently used value of depend_on_known_indirectly.
+ SET @depend_on_known_indirectly = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order WHERE id = class_id
+ );
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ @depend_on_known_indirectly IS NOT NULL AND
+ ((follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ UPDATE dhcp4_client_class_order SET depend_on_known_indirectly = 0
+ WHERE class_id = id;
+ LEAVE proc_label;
+ END IF;
+
+ IF follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SET follow_class_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = follow_class_name
+ );
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with follow_class_name does
+ -- not exist.
+ SET msg = CONCAT('Class ', follow_class_name, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp4_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp4_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1
+ ORDER BY order_index DESC;
+ END IF;
+ ELSE
+ -- A caller did not specify the follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SET follow_class_index = (SELECT MAX(order_index) FROM dhcp4_client_class_order);
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ SET follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency AS d
+ INNER JOIN dhcp4_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ SET msg = CONCAT('Unable to move class with id ', id, ' because it would break its dependencies');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ REPLACE INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_AINS AFTER INSERT ON dhcp4_client_class FOR EACH ROW BEGIN
+ CALL setClientClass4Order(NEW.id, NEW.follow_class_name, NULL);
+ CALL createAuditEntryDHCP4('dhcp4_client_class', NEW.id, "create");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_AUPD AFTER UPDATE ON dhcp4_client_class FOR EACH ROW BEGIN
+ SET @depend_on_known_directly = OLD.depend_on_known_directly;
+ SET @client_class_id = NEW.id;
+ CALL setClientClass4Order(NEW.id, NEW.follow_class_name, OLD.follow_class_name);
+ CALL createAuditEntryDHCP4('dhcp4_client_class', NEW.id, "update");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to create dhcp4_client_class audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_ADEL AFTER DELETE ON dhcp4_client_class FOR EACH ROW BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_client_class', OLD.id, "delete");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp4_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp4_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_dependency (
+ class_id BIGINT UNSIGNED NOT NULL,
+ dependency_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (class_id,dependency_id),
+ KEY dhcp4_client_class_dependency_id_idx (dependency_id),
+ CONSTRAINT dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp4_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp4_client_class (id)
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_BINS;
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - class_id id client class,
+-- - dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv4ClientClassDependency(IN class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE class_index BIGINT UNSIGNED;
+ DECLARE dependency_index BIGINT UNSIGNED;
+ DECLARE err_msg TEXT;
+
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF class_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class id must not be NULL.';
+ END IF;
+ IF dependency_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency id must not be NULL.';
+ END IF;
+ -- Dependencies on self make no sense.
+ IF class_id = dependency_id THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class must not have dependency on self.';
+ END IF;
+ -- Check position of our class in the hierarchy.
+ SET class_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.id = class_id);
+ IF class_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- Check position of the dependency.
+ SET dependency_index = (
+ SELECT o.order_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = dependency_id
+ );
+ IF dependency_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', dependency_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' must not depend on class defined later with id ', dependency_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp4_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_server AS t1
+ LEFT JOIN dhcp4_client_class_server AS t2
+ ON t2.class_id = dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ SET err_msg = CONCAT('Unmet dependencies for client class with id ', class_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_check_dependency_BINS BEFORE INSERT ON dhcp4_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS dhcp4_client_class_dependency_AINS;
+DROP PROCEDURE IF EXISTS updateDHCPv4ClientClassKnownDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE updateDHCPv4ClientClassKnownDependency(IN client_class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE dependency TINYINT;
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SET dependency = (
+ SELECT depend_on_known_directly FROM dhcp4_client_class
+ WHERE id = dependency_id
+ );
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = 0 THEN
+ SET dependency = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order
+ WHERE class_id = dependency_id
+ );
+ END IF;
+ IF dependency <> 0 THEN
+ UPDATE dhcp4_client_class_order
+ SET depend_on_known_indirectly = 1
+ WHERE class_id = client_class_id;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp4_client_class_dependency_AINS AFTER INSERT ON dhcp4_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL updateDHCPv4ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS checkDHCPv4ClientClassKnownDependencyChange;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv4 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv4ClientClassKnownDependencyChange()
+BEGIN
+ DECLARE depended TINYINT DEFAULT 0;
+ DECLARE depends TINYINT DEFAULT 0;
+
+ -- Session variables are set upon a client class update.
+ IF @client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency
+ WHERE dependency_id = @client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ IF @depend_on_known_directly <> 0 OR @depend_on_known_indirectly <> 0 THEN
+ SET depended = 1;
+ END IF;
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SET depends = (
+ SELECT depend_on_known_directly FROM dhcp4_client_class
+ WHERE id = @client_class_id
+ );
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = 0 THEN
+ SET depends = (
+ SELECT depend_on_known_indirectly FROM dhcp4_client_class_order
+ WHERE class_id = @client_class_id
+ );
+ END IF;
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.';
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv4 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_server (
+ class_id bigint unsigned NOT NULL,
+ server_id bigint unsigned NOT NULL,
+ modification_ts timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ KEY fk_dhcp4_client_class_server_id (server_id),
+ CONSTRAINT fk_dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp4_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv4 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp4_option_def
+ ADD COLUMN class_id BIGINT UNSIGNED NULL DEFAULT NULL;
+
+ALTER TABLE dhcp4_option_def
+ ADD CONSTRAINT fk_dhcp4_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv6 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp6_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class (
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ name VARCHAR(128) NOT NULL,
+ test TEXT,
+ only_if_required TINYINT NOT NULL DEFAULT '0',
+ valid_lifetime INT DEFAULT NULL,
+ min_valid_lifetime INT DEFAULT NULL,
+ max_valid_lifetime INT DEFAULT NULL,
+ depend_on_known_directly TINYINT NOT NULL DEFAULT '0',
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY id_UNIQUE (id),
+ UNIQUE KEY name_UNIQUE (name),
+ KEY key_dhcp6_client_class_modification_ts (modification_ts)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp6_client_class table has a corresponding row
+-- in the dhcp6_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp6_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp6_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_indirectly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp6_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_order (
+ class_id BIGINT UNSIGNED NOT NULL,
+ order_index BIGINT UNSIGNED NOT NULL,
+ depend_on_known_indirectly TINYINT NOT NULL DEFAULT '0',
+ PRIMARY KEY (class_id),
+ KEY key_dhcp6_client_class_order_index (order_index),
+ CONSTRAINT fk_dhcp6_client_class_order_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_AINS;
+DROP TRIGGER IF EXISTS dhcp6_client_class_AUPD;
+DROP TRIGGER IF EXISTS dhcp6_client_class_ADEL;
+DROP PROCEDURE IF EXISTS setClientClass6Order;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE setClientClass6Order(IN id BIGINT UNSIGNED,
+ IN follow_class_name VARCHAR(128),
+ IN old_follow_class_name VARCHAR(128))
+proc_label:BEGIN
+ -- This variable will be optionally set if the follow_class_name
+ -- column value is specified.
+ DECLARE follow_class_index BIGINT UNSIGNED;
+ DECLARE msg TEXT;
+
+ -- Remember currently used value of depend_on_known_indirectly.
+ SET @depend_on_known_indirectly = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order WHERE id = class_id
+ );
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ @depend_on_known_indirectly IS NOT NULL AND
+ ((follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ UPDATE dhcp6_client_class_order SET depend_on_known_indirectly = 0
+ WHERE class_id = id;
+ LEAVE proc_label;
+ END IF;
+
+ IF follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SET follow_class_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = follow_class_name
+ );
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with follow_class_name does
+ -- not exist.
+ SET msg = CONCAT('Class ', follow_class_name, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp6_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp6_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1
+ ORDER BY order_index DESC;
+ END IF;
+ ELSE
+ -- A caller did not specify the follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SET follow_class_index = (SELECT MAX(order_index) FROM dhcp6_client_class_order);
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ SET follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency AS d
+ INNER JOIN dhcp6_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ SET msg = CONCAT('Unable to move class with id ', id, ' because it would break its dependencies');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ REPLACE INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_AINS AFTER INSERT ON dhcp6_client_class FOR EACH ROW BEGIN
+ CALL setClientClass6Order(NEW.id, NEW.follow_class_name, NULL);
+ CALL createAuditEntryDHCP6('dhcp6_client_class', NEW.id, "create");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_AUPD AFTER UPDATE ON dhcp6_client_class FOR EACH ROW BEGIN
+ SET @depend_on_known_directly = OLD.depend_on_known_directly;
+ SET @client_class_id = NEW.id;
+ CALL setClientClass6Order(NEW.id, NEW.follow_class_name, OLD.follow_class_name);
+ CALL createAuditEntryDHCP6('dhcp6_client_class', NEW.id, "update");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger to create dhcp6_client_class audit.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_ADEL AFTER DELETE ON dhcp6_client_class FOR EACH ROW BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_client_class', OLD.id, "delete");
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp6_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp6_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_dependency (
+ class_id BIGINT UNSIGNED NOT NULL,
+ dependency_id BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (class_id,dependency_id),
+ KEY dhcp6_client_class_dependency_id_idx (dependency_id),
+ CONSTRAINT dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp6_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp6_client_class (id)
+) ENGINE=InnoDB;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_BINS;
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - class_id id client class,
+-- - dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv6ClientClassDependency(IN class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE class_index BIGINT UNSIGNED;
+ DECLARE dependency_index BIGINT UNSIGNED;
+ DECLARE err_msg TEXT;
+
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF class_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class id must not be NULL.';
+ END IF;
+ IF dependency_id IS NULL THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency id must not be NULL.';
+ END IF;
+ -- Dependencies on self make no sense.
+ IF class_id = dependency_id THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Client class must not have dependency on self.';
+ END IF;
+ -- Check position of our class in the hierarchy.
+ SET class_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.id = class_id);
+ IF class_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- Check position of the dependency.
+ SET dependency_index = (
+ SELECT o.order_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = dependency_id
+ );
+ IF dependency_index IS NULL THEN
+ SET err_msg = CONCAT('Client class with id ', dependency_id, ' does not exist.');
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ SET err_msg = CONCAT('Client class with id ', class_id, ' must not depend on class defined later with id ', dependency_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp6_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_server AS t1
+ LEFT JOIN dhcp6_client_class_server AS t2
+ ON t2.class_id = dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ SET err_msg = CONCAT('Unmet dependencies for client class with id ', class_id);
+ SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = err_msg;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_check_dependency_BINS BEFORE INSERT ON dhcp6_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS dhcp6_client_class_dependency_AINS;
+DROP PROCEDURE IF EXISTS updateDHCPv6ClientClassKnownDependency;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE updateDHCPv6ClientClassKnownDependency(IN client_class_id BIGINT UNSIGNED,
+ IN dependency_id BIGINT UNSIGNED)
+BEGIN
+ DECLARE dependency TINYINT;
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SET dependency = (
+ SELECT depend_on_known_directly FROM dhcp6_client_class
+ WHERE id = dependency_id
+ );
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = 0 THEN
+ SET dependency = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order
+ WHERE class_id = dependency_id
+ );
+ END IF;
+ IF dependency <> 0 THEN
+ UPDATE dhcp6_client_class_order
+ SET depend_on_known_indirectly = 1
+ WHERE class_id = client_class_id;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE TRIGGER dhcp6_client_class_dependency_AINS AFTER INSERT ON dhcp6_client_class_dependency FOR EACH ROW
+BEGIN
+ CALL updateDHCPv6ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS checkDHCPv6ClientClassKnownDependencyChange;
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv6 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+DELIMITER $$
+CREATE PROCEDURE checkDHCPv6ClientClassKnownDependencyChange()
+BEGIN
+ DECLARE depended TINYINT DEFAULT 0;
+ DECLARE depends TINYINT DEFAULT 0;
+
+ -- Session variables are set upon a client class update.
+ IF @client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency
+ WHERE dependency_id = @client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ IF @depend_on_known_directly <> 0 OR @depend_on_known_indirectly <> 0 THEN
+ SET depended = 1;
+ END IF;
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SET depends = (
+ SELECT depend_on_known_directly FROM dhcp6_client_class
+ WHERE id = @client_class_id
+ );
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = 0 THEN
+ SET depends = (
+ SELECT depend_on_known_indirectly FROM dhcp6_client_class_order
+ WHERE class_id = @client_class_id
+ );
+ END IF;
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ SIGNAL SQLSTATE '45000'
+ SET MESSAGE_TEXT = 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.';
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv6 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_server (
+ class_id bigint unsigned NOT NULL,
+ server_id bigint unsigned NOT NULL,
+ modification_ts timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ KEY fk_dhcp6_client_class_server_id (server_id),
+ CONSTRAINT fk_dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp6_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+) ENGINE=InnoDB;
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv6 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp6_option_def
+ ADD COLUMN class_id BIGINT UNSIGNED NULL DEFAULT NULL;
+
+ALTER TABLE dhcp6_option_def
+ ADD CONSTRAINT fk_dhcp6_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+# Update the schema version number
+UPDATE schema_version
+ SET version = '10', minor = '0';
+
+# This line concludes database upgrade to version 10.
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_010_to_011.sh.in b/src/share/database/scripts/mysql/upgrade_010_to_011.sh.in
new file mode 100644
index 0000000..c5f512a
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_010_to_011.sh.in
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+# Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+# Check version.
+version=$(mysql_version "${@}")
+if test "${version}" != "10.0"; then
+ printf 'This script upgrades 10.0 to 11.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${version}"
+ exit 0
+fi
+
+# Get the schema name from database argument. We need this to
+# query information_schema for the right database.
+for arg in "${@}"
+do
+ if ! printf '%s' "${arg}" | grep -Eq '^\-\-'
+ then
+ schema="$arg"
+ break
+ fi
+done
+
+# Make sure we have the schema.
+if [ -z "$schema" ]
+then
+ printf "Could not find database schema name in cmd line args: %s\n" "${*}"
+ exit 255
+fi
+
+mysql "$@" <<EOF
+
+-- Reverse index order to improve reclamation query performance
+-- Add a constraint that any state value added to the lease4 must
+-- map to a value in the lease_state table. Note we have to drop
+-- the foreign keys first, then add them back.
+ALTER TABLE lease4 DROP FOREIGN KEY fk_lease4_state;
+DROP INDEX lease4_by_state_expire ON lease4;
+CREATE INDEX lease4_by_expire_state ON lease4 (expire ASC, state ASC);
+ALTER TABLE lease4
+ ADD CONSTRAINT fk_lease4_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+ALTER TABLE lease6 DROP FOREIGN KEY fk_lease6_state;
+CREATE INDEX lease6_by_expire_state ON lease6 (expire ASC, state ASC);
+DROP INDEX lease6_by_state_expire ON lease6;
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+-- Added preferred lifetime columns to v6 client classes
+ALTER TABLE dhcp6_client_class
+ ADD COLUMN preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN min_preferred_lifetime INT(10) DEFAULT NULL,
+ ADD COLUMN max_preferred_lifetime INT(10) DEFAULT NULL;
+
+-- Update the schema version number
+UPDATE schema_version
+ SET version = '11', minor = '0';
+
+-- This line concludes database upgrade to version 11.0
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_011_to_012.sh.in b/src/share/database/scripts/mysql/upgrade_011_to_012.sh.in
new file mode 100644
index 0000000..c4142b2
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_011_to_012.sh.in
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+# Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+# Check version.
+version=$(mysql_version "${@}")
+if test "${version}" != "11.0"; then
+ printf 'This script upgrades 11.0 to 12.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${version}"
+ exit 0
+fi
+
+# Get the schema name from database argument. We need this to
+# query information_schema for the right database.
+for arg in "${@}"
+do
+ if ! printf '%s' "${arg}" | grep -Eq '^\-\-'
+ then
+ schema="$arg"
+ break
+ fi
+done
+
+# Make sure we have the schema.
+if [ -z "$schema" ]
+then
+ printf "Could not find database schema name in cmd line args: %s\n" "${*}"
+ exit 255
+fi
+
+mysql "$@" <<EOF
+
+-- Ensure that dhcp_client_class is NULL when an option does not
+-- belong to any client class. Later, we will add foreign keys for
+-- the dhcp_client_class columns, and it requires that the columns
+-- are NULL when there are no corresponding client classes.
+SET @disable_audit = 1;
+UPDATE dhcp4_options SET dhcp_client_class = NULL
+ WHERE scope_id <> 2;
+UPDATE dhcp6_options SET dhcp_client_class = NULL
+ WHERE scope_id <> 2;
+SET @disable_audit = 0;
+
+-- Add a foreign keys referencing a client classes. If an option is
+-- associated with a client class, the option will be deleted
+-- along with the deleted client class.
+ALTER TABLE dhcp4_options
+ ADD CONSTRAINT fk_dhcp4_options_client_class
+ FOREIGN KEY (dhcp_client_class)
+ REFERENCES dhcp4_client_class (name)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_options
+ ADD CONSTRAINT fk_dhcp6_options_client_class
+ FOREIGN KEY (dhcp_client_class)
+ REFERENCES dhcp6_client_class (name)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- Update the schema version number
+UPDATE schema_version
+ SET version = '12', minor = '0';
+
+-- This line concludes database upgrade to version 12.
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_012_to_013.sh.in b/src/share/database/scripts/mysql/upgrade_012_to_013.sh.in
new file mode 100644
index 0000000..ee133d5
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_012_to_013.sh.in
@@ -0,0 +1,265 @@
+#!/bin/sh
+
+# Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+# Check version.
+version=$(mysql_version "${@}")
+if test "${version}" != "12.0"; then
+ printf 'This script upgrades 12.0 to 13.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${version}"
+ exit 0
+fi
+
+# Get the schema name from database argument. We need this to
+# query information_schema for the right database.
+for arg in "${@}"
+do
+ if ! printf '%s' "${arg}" | grep -Eq '^\-\-'
+ then
+ schema="$arg"
+ break
+ fi
+done
+
+# Make sure we have the schema.
+if [ -z "$schema" ]
+then
+ printf "Could not find database schema name in cmd line args: %s\n" "${*}"
+ exit 255
+fi
+
+mysql "$@" <<EOF
+
+-- Create a function that separates a contiguous hexadecimal string
+-- into groups of two hexadecimals separated by colons.
+DROP FUNCTION IF EXISTS colonSeparatedHex;
+DELIMITER $$
+CREATE FUNCTION colonSeparatedHex(hex VARCHAR(64))
+RETURNS VARCHAR(64)
+DETERMINISTIC
+BEGIN
+ -- Declarations
+ DECLARE i INT;
+ DECLARE length INT;
+ DECLARE output VARCHAR(64);
+
+ -- Initializations
+ SET i = 3;
+ SET length = LENGTH(hex);
+
+ -- Add a leading zero if the first octet has a single hexadecimal character.
+ IF MOD(length, 2) = 1 THEN
+ SET hex = CONCAT('0', hex);
+ SET length = length + 1;
+ END IF;
+
+ -- Start with the first octet.
+ SET output = SUBSTR(hex, 1, 2);
+
+ -- Add one octet at a time and a leading colon with each.
+ label: WHILE i < length DO
+ SET output = CONCAT(output, ':', SUBSTR(hex, i, 2));
+ SET i = i + 2;
+ END WHILE label;
+
+ -- Memfile uses lowercase hexadecimals.
+ SET output = LOWER(output);
+
+ RETURN output;
+END $$
+DELIMITER ;
+
+-- Modify the procedure to output a memfile-ready CSV file.
+DROP PROCEDURE IF EXISTS lease4DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease4DumpData()
+BEGIN
+ SELECT
+ INET_NTOA(address),
+ IFNULL(colonSeparatedHex(HEX(hwaddr)), ''),
+ IFNULL(colonSeparatedHex(HEX(client_id)), ''),
+ valid_lifetime,
+ UNIX_TIMESTAMP(expire),
+ subnet_id,
+ fqdn_fwd,
+ fqdn_rev,
+ REPLACE(hostname, ',', '&#x2c'),
+ state,
+ REPLACE(IFNULL(user_context, ''), ',', '&#x2c')
+ FROM lease4
+ ORDER BY address;
+END $$
+DELIMITER ;
+
+-- hwtype and hwaddr_source need to be last to match memfile format.
+DROP PROCEDURE IF EXISTS lease6DumpHeader;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpHeader()
+BEGIN
+ SELECT 'address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state,user_context,hwtype,hwaddr_source';
+END $$
+DELIMITER ;
+
+-- Modify the procedure to output a memfile-ready CSV file.
+DROP PROCEDURE IF EXISTS lease6DumpData;
+DELIMITER $$
+CREATE PROCEDURE lease6DumpData()
+BEGIN
+ SELECT
+ address,
+ IFNULL(colonSeparatedHex(HEX(duid)), ''),
+ valid_lifetime,
+ UNIX_TIMESTAMP(expire),
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd,
+ fqdn_rev,
+ REPLACE(hostname, ',', '&#x2c'),
+ IFNULL(colonSeparatedHex(HEX(hwaddr)), ''),
+ state,
+ REPLACE(IFNULL(user_context, ''), ',', '&#x2c'),
+ hwtype,
+ hwaddr_source
+ FROM lease6
+ ORDER BY address;
+END $$
+DELIMITER ;
+
+-- Create a procedure that inserts a v4 lease from memfile data.
+DELIMITER $$
+CREATE PROCEDURE lease4Upload(
+ IN address VARCHAR(15),
+ IN hwaddr VARCHAR(20),
+ IN client_id VARCHAR(128),
+ IN valid_lifetime INT UNSIGNED,
+ IN expire BIGINT UNSIGNED,
+ IN subnet_id INT UNSIGNED,
+ IN fqdn_fwd TINYINT,
+ IN fqdn_rev TINYINT,
+ IN hostname VARCHAR(255),
+ IN state INT UNSIGNED,
+ IN user_context TEXT
+)
+BEGIN
+ INSERT INTO lease4 (
+ address,
+ hwaddr,
+ client_id,
+ valid_lifetime,
+ expire,
+ subnet_id,
+ fqdn_fwd,
+ fqdn_rev,
+ hostname,
+ state,
+ user_context
+ ) VALUES (
+ INET_ATON(address),
+ UNHEX(REPLACE(hwaddr, ':', '')),
+ UNHEX(REPLACE(client_id, ':', '')),
+ valid_lifetime,
+ FROM_UNIXTIME(expire),
+ subnet_id,
+ fqdn_fwd,
+ fqdn_rev,
+ REPLACE(hostname, '&#x2c', ','),
+ state,
+ REPLACE(user_context, '&#x2c', ',')
+ );
+END $$
+DELIMITER ;
+
+-- Create a procedure that inserts a v6 lease from memfile data.
+DELIMITER $$
+CREATE PROCEDURE lease6Upload(
+ IN address VARCHAR(39),
+ IN duid VARCHAR(128),
+ IN valid_lifetime INT UNSIGNED,
+ IN expire BIGINT UNSIGNED,
+ IN subnet_id INT UNSIGNED,
+ IN pref_lifetime INT UNSIGNED,
+ IN lease_type TINYINT,
+ IN iaid INT UNSIGNED,
+ IN prefix_len TINYINT UNSIGNED,
+ IN fqdn_fwd TINYINT,
+ IN fqdn_rev TINYINT,
+ IN hostname VARCHAR(255),
+ IN hwaddr VARCHAR(64),
+ IN state INT UNSIGNED,
+ IN user_context TEXT,
+ IN hwtype SMALLINT,
+ IN hwaddr_source INT UNSIGNED
+)
+BEGIN
+ INSERT INTO lease6 (
+ address,
+ duid,
+ valid_lifetime,
+ expire,
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd,
+ fqdn_rev,
+ hostname,
+ hwaddr,
+ state,
+ user_context,
+ hwtype,
+ hwaddr_source
+ ) VALUES (
+ address,
+ UNHEX(REPLACE(duid, ':', '')),
+ valid_lifetime,
+ FROM_UNIXTIME(expire),
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd,
+ fqdn_rev,
+ REPLACE(hostname, '&#x2c', ','),
+ UNHEX(REPLACE(hwaddr, ':', '')),
+ state,
+ REPLACE(user_context, '&#x2c', ','),
+ hwtype,
+ hwaddr_source
+ );
+END $$
+DELIMITER ;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '13', minor = '0';
+
+-- This line concludes database upgrade to version 13.
+EOF
diff --git a/src/share/database/scripts/mysql/upgrade_013_to_014.sh.in b/src/share/database/scripts/mysql/upgrade_013_to_014.sh.in
new file mode 100644
index 0000000..b442ef3
--- /dev/null
+++ b/src/share/database/scripts/mysql/upgrade_013_to_014.sh.in
@@ -0,0 +1,826 @@
+#!/bin/sh
+
+# Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+# Check version.
+version=$(mysql_version "${@}")
+if test "${version}" != "13.0"; then
+ printf 'This script upgrades 13.0 to 14.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${version}"
+ exit 0
+fi
+
+# Get the schema name from database argument. We need this to
+# query information_schema for the right database.
+for arg in "${@}"
+do
+ if ! printf '%s' "${arg}" | grep -Eq '^\-\-'
+ then
+ schema="$arg"
+ break
+ fi
+done
+
+# Make sure we have the schema.
+if [ -z "$schema" ]
+then
+ printf "Could not find database schema name in cmd line args: %s\n" "${*}"
+ exit 255
+fi
+
+mysql "$@" <<EOF
+-- This line starts the schema upgrade to version 14.
+
+-- Modify shared-network-name foreign key constraint on dhcp4_subnet to not perform
+-- the update when the network is deleted the cascaded update will not execute
+-- dhcp4_subnet update trigger leaving the updated subnets without audit_entries.
+ALTER TABLE dhcp4_subnet
+ DROP FOREIGN KEY fk_dhcp4_subnet_shared_network;
+
+ALTER TABLE dhcp4_subnet
+ ADD CONSTRAINT fk_dhcp4_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network (name)
+ ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+-- Modify BEFORE delete trigger on dhcp4_shared_network to explicitly
+-- update dhcp4_subnets. This ensures there are audit entries for updated
+-- subnets.
+DROP TRIGGER dhcp4_shared_network_BDEL;
+
+DELIMITER $$
+CREATE TRIGGER dhcp4_shared_network_BDEL BEFORE DELETE ON dhcp4_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, "delete");
+ -- In MySQL Foreign key constraint triggered updates will not cascade, so we explicitly
+ -- update subnets first which should ensure they get audit entries.
+ UPDATE dhcp4_subnet SET shared_network_name = NULL WHERE shared_network_name = OLD.name;
+ DELETE FROM dhcp4_options WHERE shared_network_name = OLD.name;
+ END $$
+DELIMITER ;
+
+-- Modify shared-network-name foreign key constraint on dhcp6_subnet to not perform
+-- the update when the network is deleted the cascaded update will not execute
+-- dhcp6_subnet update trigger leaving the updated subnets without audit_entries.
+ALTER TABLE dhcp6_subnet
+ DROP FOREIGN KEY fk_dhcp6_subnet_shared_network;
+
+ALTER TABLE dhcp6_subnet
+ ADD CONSTRAINT fk_dhcp6_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network (name)
+ ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+-- Modify BEFORE delete trigger on dhcp6_shared_network to explicitly
+-- update dhcp6_subnets. This ensures there are audit entries for updated
+-- subnets.
+DROP TRIGGER dhcp6_shared_network_BDEL;
+
+DELIMITER $$
+CREATE TRIGGER dhcp6_shared_network_BDEL BEFORE DELETE ON dhcp6_shared_network
+ FOR EACH ROW
+ BEGIN
+ CALL createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, "delete");
+ -- In MySQL Foreign key constraint triggered updates will not cascade, so we explicitly
+ -- update subnets first which should ensure they get audit entries.
+ UPDATE dhcp6_subnet SET shared_network_name = NULL WHERE shared_network_name = OLD.name;
+ DELETE FROM dhcp6_options WHERE shared_network_name = OLD.name;
+ END $$
+DELIMITER ;
+
+-- Add user_context column to client class tables.
+ALTER TABLE dhcp4_client_class ADD COLUMN user_context LONGTEXT NULL;
+ALTER TABLE dhcp6_client_class ADD COLUMN user_context LONGTEXT NULL;
+
+-- Schema changes related to lease limiting start here. --
+
+-- Recreate the triggers that update the leaseX_stat tables as stored procedures. --
+
+DROP PROCEDURE IF EXISTS lease4_AINS_lease4_stat;
+DELIMITER $$
+CREATE PROCEDURE lease4_AINS_lease4_stat(IN new_state TINYINT,
+ IN new_subnet_id INT UNSIGNED)
+BEGIN
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Update the state count if it exists.
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND state = new_state;
+
+ -- Insert the state count record if it does not exist.
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat VALUES (new_subnet_id, new_state, 1);
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease4_AUPD_lease4_stat;
+DELIMITER $$
+CREATE PROCEDURE lease4_AUPD_lease4_stat(IN old_state TINYINT,
+ IN old_subnet_id INT UNSIGNED,
+ IN new_state TINYINT,
+ IN new_subnet_id INT UNSIGNED)
+BEGIN
+ IF old_subnet_id != new_subnet_id OR old_state != new_state THEN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the old state count if record exists.
+ UPDATE lease4_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND state = old_state;
+ END IF;
+
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Increment the new state count if record exists.
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND state = new_state;
+
+ -- Insert new state record if it does not exist.
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat VALUES (new_subnet_id, new_state, 1);
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease4_ADEL_lease4_stat;
+DELIMITER $$
+CREATE PROCEDURE lease4_ADEL_lease4_stat(IN old_state TINYINT,
+ IN old_subnet_id INT UNSIGNED)
+BEGIN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the state count if record exists.
+ UPDATE lease4_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND old_state = state;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_AINS_lease6_stat;
+DELIMITER $$
+CREATE PROCEDURE lease6_AINS_lease6_stat(IN new_state TINYINT,
+ IN new_subnet_id INT UNSIGNED,
+ IN new_lease_type TINYINT)
+BEGIN
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Update the state count if it exists.
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND lease_type = new_lease_type
+ AND state = new_state;
+
+ -- Insert the state count record if it does not exist.
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat VALUES (new_subnet_id, new_lease_type, new_state, 1);
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_AUPD_lease6_stat;
+DELIMITER $$
+CREATE PROCEDURE lease6_AUPD_lease6_stat(IN old_state TINYINT,
+ IN old_subnet_id INT UNSIGNED,
+ IN old_lease_type TINYINT,
+ IN new_state TINYINT,
+ IN new_subnet_id INT UNSIGNED,
+ IN new_lease_type TINYINT)
+BEGIN
+ IF old_subnet_id != new_subnet_id OR
+ old_lease_type != new_lease_type OR
+ old_state != new_state THEN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the old state count if record exists.
+ UPDATE lease6_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND lease_type = old_lease_type
+ AND state = old_state;
+ END IF;
+
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND lease_type = new_lease_type
+ AND state = new_state;
+
+ -- Insert new state record if it does not exist
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat
+ VALUES (new_subnet_id, new_lease_type, new_state, 1);
+ END IF;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_ADEL_lease6_stat;
+DELIMITER $$
+CREATE PROCEDURE lease6_ADEL_lease6_stat(IN old_state TINYINT,
+ IN old_subnet_id INT UNSIGNED,
+ IN old_lease_type TINYINT)
+BEGIN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease6_stat
+ SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND lease_type = old_lease_type
+ AND state = old_state;
+ END IF;
+END $$
+DELIMITER ;
+
+-- Create tables that contain the number of active leases. --
+
+DROP TABLE IF EXISTS lease4_stat_by_client_class;
+CREATE TABLE lease4_stat_by_client_class (
+ client_class VARCHAR(128) NOT NULL PRIMARY KEY,
+ leases BIGINT UNSIGNED NOT NULL
+) ENGINE = InnoDB;
+
+DROP TABLE IF EXISTS lease6_stat_by_client_class;
+CREATE TABLE lease6_stat_by_client_class (
+ client_class VARCHAR(128) NOT NULL,
+ lease_type TINYINT NOT NULL,
+ leases BIGINT UNSIGNED NOT NULL,
+ PRIMARY KEY (client_class, lease_type),
+ CONSTRAINT fk_lease6_stat_by_client_class_lease_type FOREIGN KEY (lease_type)
+ REFERENCES lease6_types (lease_type)
+) ENGINE = InnoDB;
+
+-- Create procedures to be called for each row in after-event triggers for
+-- INSERT, UPDATE and DELETE on lease tables.
+
+DROP PROCEDURE IF EXISTS lease4_AINS_lease4_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease4_AINS_lease4_stat_by_client_class(IN new_state TINYINT,
+ IN new_user_context TEXT)
+BEGIN
+ -- Declarations
+ DECLARE client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ -- Ignore ERROR 3141 (22032) at line 1: Invalid JSON text in argument 1 to
+ -- function json_extract: "The document is empty." at position 0.
+ -- Ignore ERROR 4037 (HY000): Unexpected end of JSON text in argument 1 to function 'json_extract'
+ -- These situations are handled with a propagating NULL result from JSON_EXTRACT.
+ DECLARE CONTINUE HANDLER FOR 3141 BEGIN END;
+ DECLARE CONTINUE HANDLER FOR 4037 BEGIN END;
+
+ -- Only state 0 is needed for lease limiting.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ SET client_classes = JSON_EXTRACT(new_user_context, '$."ISC"."client-classes"');
+ SET length = JSON_LENGTH(client_classes);
+
+ -- Iterate through all the client classes and increment the lease count for each.
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(client_classes, CONCAT('\$[', i, ']')));
+
+ -- Upsert to increment the lease count.
+ UPDATE lease4_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class;
+ IF ROW_COUNT() = 0 THEN
+ INSERT INTO lease4_stat_by_client_class VALUES (class, 1);
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease4_AUPD_lease4_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease4_AUPD_lease4_stat_by_client_class(IN old_state TINYINT,
+ IN old_user_context TEXT,
+ IN new_state TINYINT,
+ IN new_user_context TEXT)
+BEGIN
+ -- Declarations
+ DECLARE old_client_classes TEXT;
+ DECLARE new_client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ SET old_client_classes = JSON_EXTRACT(old_user_context, '$."ISC"."client-classes"');
+ SET new_client_classes = JSON_EXTRACT(new_user_context, '$."ISC"."client-classes"');
+
+ IF old_state != new_state OR old_client_classes != new_client_classes THEN
+ -- Check if it's moving away from a counted state.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ SET length = JSON_LENGTH(old_client_classes);
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(old_client_classes, CONCAT('\$[', i, ']')));
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease4_stat_by_client_class SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE client_class = class;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+
+ -- Check if it's moving into a counted state.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ SET length = JSON_LENGTH(new_client_classes);
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(new_client_classes, CONCAT('\$[', i, ']')));
+
+ -- Upsert to increment the lease count.
+ UPDATE lease4_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class;
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease4_stat_by_client_class VALUES (class, 1);
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease4_ADEL_lease4_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease4_ADEL_lease4_stat_by_client_class(IN old_state TINYINT,
+ IN old_user_context TEXT)
+BEGIN
+ -- Declarations
+ DECLARE client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ -- Ignore ERROR 3141 (22032) at line 1: Invalid JSON text in argument 1 to
+ -- function json_extract: "The document is empty." at position 0.
+ -- Ignore ERROR 4037 (HY000): Unexpected end of JSON text in argument 1 to function 'json_extract'
+ -- These situations are handled with a propagating NULL result from JSON_EXTRACT.
+ DECLARE CONTINUE HANDLER FOR 3141 BEGIN END;
+ DECLARE CONTINUE HANDLER FOR 4037 BEGIN END;
+
+ -- Only state 0 is accounted for in lease limiting.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ SET client_classes = JSON_EXTRACT(old_user_context, '$."ISC"."client-classes"');
+ SET length = JSON_LENGTH(client_classes);
+
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(client_classes, CONCAT('\$[', i, ']')));
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease4_stat_by_client_class SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE client_class = class;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_AINS_lease6_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease6_AINS_lease6_stat_by_client_class(IN new_state TINYINT,
+ IN new_user_context TEXT,
+ IN new_lease_type TINYINT)
+BEGIN
+ -- Declarations
+ DECLARE client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ -- Ignore ERROR 3141 (22032) at line 1: Invalid JSON text in argument 1 to
+ -- function json_extract: "The document is empty." at position 0.
+ -- Ignore ERROR 4037 (HY000): Unexpected end of JSON text in argument 1 to function 'json_extract'
+ -- These situations are handled with a propagating NULL result from JSON_EXTRACT.
+ DECLARE CONTINUE HANDLER FOR 3141 BEGIN END;
+ DECLARE CONTINUE HANDLER FOR 4037 BEGIN END;
+
+ -- Only state 0 is needed for lease limiting.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ SET client_classes = JSON_EXTRACT(new_user_context, '$."ISC"."client-classes"');
+ SET length = JSON_LENGTH(client_classes);
+
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(client_classes, CONCAT('\$[', i, ']')));
+
+ -- Upsert to increment the lease count.
+ UPDATE lease6_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class AND lease_type = new_lease_type;
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat_by_client_class VALUES (class, new_lease_type, 1);
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_AUPD_lease6_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease6_AUPD_lease6_stat_by_client_class(IN old_state TINYINT,
+ IN old_user_context TEXT,
+ IN old_lease_type TINYINT,
+ IN new_state TINYINT,
+ IN new_user_context TEXT,
+ IN new_lease_type TINYINT)
+BEGIN
+ -- Declarations
+ DECLARE old_client_classes TEXT;
+ DECLARE new_client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ SET old_client_classes = JSON_EXTRACT(old_user_context, '$."ISC"."client-classes"');
+ SET new_client_classes = JSON_EXTRACT(new_user_context, '$."ISC"."client-classes"');
+
+ IF old_state != new_state OR old_client_classes != new_client_classes OR old_lease_type != new_lease_type THEN
+ -- Check if it's moving away from a counted state.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ SET length = JSON_LENGTH(old_client_classes);
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(old_client_classes, CONCAT('\$[', i, ']')));
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease6_stat_by_client_class SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE client_class = class AND lease_type = old_lease_type;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+
+ -- Check if it's moving into a counted state.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ SET length = JSON_LENGTH(new_client_classes);
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(new_client_classes, CONCAT('\$[', i, ']')));
+
+ -- Upsert to increment the lease count.
+ UPDATE lease6_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class AND lease_type = new_lease_type;
+ IF ROW_COUNT() <= 0 THEN
+ INSERT INTO lease6_stat_by_client_class VALUES (class, new_lease_type, 1);
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+ END IF;
+END $$
+DELIMITER ;
+
+DROP PROCEDURE IF EXISTS lease6_ADEL_lease6_stat_by_client_class;
+DELIMITER $$
+CREATE PROCEDURE lease6_ADEL_lease6_stat_by_client_class(IN old_state TINYINT,
+ IN old_user_context TEXT,
+ IN old_lease_type TINYINT)
+BEGIN
+ -- Declarations
+ DECLARE client_classes TEXT;
+ DECLARE class VARCHAR(255);
+ DECLARE length INT;
+ DECLARE i INT;
+
+ -- Ignore ERROR 3141 (22032) at line 1: Invalid JSON text in argument 1 to
+ -- function json_extract: "The document is empty." at position 0.
+ -- Ignore ERROR 4037 (HY000): Unexpected end of JSON text in argument 1 to function 'json_extract'
+ -- These situations are handled with a propagating NULL result from JSON_EXTRACT.
+ DECLARE CONTINUE HANDLER FOR 3141 BEGIN END;
+ DECLARE CONTINUE HANDLER FOR 4037 BEGIN END;
+
+ -- Only state 0 is accounted for in lease limiting. But check both states to be consistent with lease6_stat.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ SET client_classes = JSON_EXTRACT(old_user_context, '$."ISC"."client-classes"');
+ SET length = JSON_LENGTH(client_classes);
+
+ SET i = 0;
+ label: WHILE i < length DO
+ SET class = JSON_UNQUOTE(JSON_EXTRACT(client_classes, CONCAT('\$[', i, ']')));
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease6_stat_by_client_class SET leases = IF(leases > 0, leases - 1, 0)
+ WHERE client_class = class AND lease_type = old_lease_type;
+
+ SET i = i + 1;
+ END WHILE label;
+ END IF;
+END $$
+DELIMITER ;
+
+-- Recreate the after-event triggers for INSERT, UPDATE and DELETE on lease tables to call the --
+-- stored procedures above in pairs of two: for client classes and for subnets. --
+
+-- Function that establishes whether JSON functions are supported.
+-- They should be provided with MySQL>= 5.7, MariaDB >= 10.2.3.
+DROP FUNCTION IF EXISTS isJsonSupported;
+DELIMITER $$
+CREATE FUNCTION isJsonSupported()
+RETURNS BOOL
+DETERMINISTIC
+BEGIN
+ DECLARE dummy BOOL;
+ DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
+ RETURN false;
+
+ SELECT JSON_EXTRACT('{ "foo": 1 }', '$.foo') INTO dummy;
+ RETURN true;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease4_insert;
+DROP TRIGGER IF EXISTS lease4_AINS;
+DELIMITER $$
+CREATE TRIGGER lease4_AINS AFTER INSERT ON lease4 FOR EACH ROW
+BEGIN
+ CALL lease4_AINS_lease4_stat(NEW.state, NEW.subnet_id);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease4_AINS_lease4_stat_by_client_class(NEW.state, NEW.user_context);
+ END IF;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease4_update;
+DROP TRIGGER IF EXISTS lease4_AUPD;
+DELIMITER $$
+CREATE TRIGGER lease4_AUPD AFTER UPDATE ON lease4 FOR EACH ROW
+BEGIN
+ CALL lease4_AUPD_lease4_stat(OLD.state, OLD.subnet_id, NEW.state, NEW.subnet_id);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease4_AUPD_lease4_stat_by_client_class(OLD.state, OLD.user_context, NEW.state, NEW.user_context);
+ END IF;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease4_delete;
+DROP TRIGGER IF EXISTS lease4_ADEL;
+DELIMITER $$
+CREATE TRIGGER lease4_ADEL AFTER DELETE ON lease4 FOR EACH ROW
+BEGIN
+ CALL lease4_ADEL_lease4_stat(OLD.state, OLD.subnet_id);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease4_ADEL_lease4_stat_by_client_class(OLD.state, OLD.user_context);
+ END IF;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease6_insert;
+DROP TRIGGER IF EXISTS lease6_AINS;
+DELIMITER $$
+CREATE TRIGGER lease6_AINS AFTER INSERT ON lease6 FOR EACH ROW
+BEGIN
+ CALL lease6_AINS_lease6_stat(NEW.state, NEW.subnet_id, NEW.lease_type);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease6_AINS_lease6_stat_by_client_class(NEW.state, NEW.user_context, NEW.lease_type);
+ END IF;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease6_update;
+DROP TRIGGER IF EXISTS lease6_AUPD;
+DELIMITER $$
+CREATE TRIGGER lease6_AUPD AFTER UPDATE ON lease6 FOR EACH ROW
+BEGIN
+ CALL lease6_AUPD_lease6_stat(OLD.state, OLD.subnet_id, OLD.lease_type, NEW.state, NEW.subnet_id, NEW.lease_type);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease6_AUPD_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type, NEW.state, NEW.user_context, NEW.lease_type);
+ END IF;
+END $$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS stat_lease6_delete;
+DROP TRIGGER IF EXISTS lease6_ADEL;
+DELIMITER $$
+CREATE TRIGGER lease6_ADEL AFTER DELETE ON lease6 FOR EACH ROW
+BEGIN
+ CALL lease6_ADEL_lease6_stat(OLD.state, OLD.subnet_id, OLD.lease_type);
+ IF @json_supported IS NULL THEN
+ SELECT isJsonSupported() INTO @json_supported;
+ END IF;
+ IF @json_supported = 1 THEN
+ CALL lease6_ADEL_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type);
+ END IF;
+END $$
+DELIMITER ;
+
+-- Create functions that check if the lease limits set in the given user context are exceeded.
+-- They return a string describing a limit that is being exceeded, or an empty
+-- string if no limits are exceeded. The following format for is assumed for user_context
+-- (not all nodes are mandatory and values are given only as examples):
+-- { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+-- "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+
+DROP FUNCTION IF EXISTS checkLease4Limits;
+DELIMITER $$
+CREATE FUNCTION checkLease4Limits(user_context TEXT)
+RETURNS TEXT
+READS SQL DATA
+BEGIN
+ -- Declarations
+ DECLARE json_element TEXT;
+ DECLARE length INT;
+ DECLARE class TEXT;
+ DECLARE name VARCHAR(255);
+ DECLARE i INT;
+ DECLARE lease_limit INT;
+ DECLARE lease_count INT;
+
+ -- Dive into client class limits.
+ SET json_element = JSON_EXTRACT(user_context, '$."ISC"."limits"."client-classes"');
+ SET length = JSON_LENGTH(json_element);
+
+ SET i = 0;
+ label: WHILE i < length DO
+ -- Get the lease limit for this client class.
+ SET class = JSON_EXTRACT(json_element, CONCAT('\$[', i, ']'));
+ SET name = JSON_UNQUOTE(JSON_EXTRACT(class, '$.name'));
+ SET lease_limit = JSON_EXTRACT(class, '$."address-limit"');
+
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SET lease_count = (SELECT leases FROM lease4_stat_by_client_class WHERE client_class = name);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+
+ -- Dive into subnet limits. Reuse i as subnet ID.
+ SET json_element = JSON_EXTRACT(user_context, '$.ISC.limits.subnet');
+ SET i = JSON_EXTRACT(json_element, '$.id');
+ SET lease_limit = JSON_EXTRACT(json_element, '$."address-limit"');
+
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SET lease_count = (SELECT leases FROM lease4_stat WHERE subnet_id = i AND state = 0);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for subnet ID ', i, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ RETURN '';
+END $$
+DELIMITER ;
+
+DROP FUNCTION IF EXISTS checkLease6Limits;
+DELIMITER $$
+CREATE FUNCTION checkLease6Limits(user_context TEXT)
+RETURNS TEXT
+READS SQL DATA
+BEGIN
+ -- Declarations
+ DECLARE json_element TEXT;
+ DECLARE length INT;
+ DECLARE class TEXT;
+ DECLARE name VARCHAR(255);
+ DECLARE i INT;
+ DECLARE lease_limit INT;
+ DECLARE lease_count INT;
+
+ -- Dive into client class limits.
+ SET json_element = JSON_EXTRACT(user_context, '$."ISC"."limits"."client-classes"');
+ SET length = JSON_LENGTH(json_element);
+
+ SET i = 0;
+ label: WHILE i < length DO
+ -- Get the lease limit for this client class.
+ SET class = JSON_EXTRACT(json_element, CONCAT('\$[', i, ']'));
+ SET name = JSON_UNQUOTE(JSON_EXTRACT(class, '$.name'));
+
+ SET lease_limit = JSON_EXTRACT(class, '$."address-limit"');
+ IF lease_limit IS NOT NULL THEN
+ -- Get the address count for this client class.
+ SET lease_count = (SELECT leases FROM lease6_stat_by_client_class WHERE client_class = name AND lease_type = 0);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ SET lease_limit = JSON_EXTRACT(class, '$."prefix-limit"');
+ IF lease_limit IS NOT NULL THEN
+ -- Get the prefix count for this client class.
+ SET lease_count = (SELECT leases FROM lease6_stat_by_client_class WHERE client_class = name AND lease_type = 2);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('prefix limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ SET i = i + 1;
+ END WHILE label;
+
+ -- Dive into subnet limits. Reuse i as subnet ID.
+ SET json_element = JSON_EXTRACT(user_context, '$.ISC.limits.subnet');
+ SET i = JSON_EXTRACT(json_element, '$.id');
+ SET lease_limit = JSON_EXTRACT(json_element, '$."address-limit"');
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SET lease_count = (SELECT leases FROM lease6_stat WHERE subnet_id = i AND lease_type = 0 AND state = 0);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for subnet ID ', i, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+ SET lease_limit = JSON_EXTRACT(json_element, '$."prefix-limit"');
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SET lease_count = (SELECT leases FROM lease6_stat WHERE subnet_id = i AND lease_type = 2 AND state = 0);
+ IF lease_count IS NULL THEN
+ SET lease_count = 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is exceeded.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('prefix limit ', lease_limit, ' for subnet ID ', i, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ RETURN '';
+END $$
+DELIMITER ;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '14', minor = '0';
+
+-- This line concludes the schema upgrade to version 14.
+EOF
diff --git a/src/share/database/scripts/mysql/wipe_data.sh.in b/src/share/database/scripts/mysql/wipe_data.sh.in
new file mode 100644
index 0000000..83a6f3f
--- /dev/null
+++ b/src/share/database/scripts/mysql/wipe_data.sh.in
@@ -0,0 +1,118 @@
+#!/bin/sh
+
+# Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# This script is primarily used for MySQL unit tests, which need to
+# ensure an empty, but schema correct database for each test. It
+# deletes ALL transient data from an existing Kea MySQL schema,
+# including leases, reservations, etc... Use at your own peril.
+# Reference tables will be left in-tact.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+# First argument must be the expected schema version <major>.<minor>
+# Check if it's passed at all.
+if [ "$#" -lt "1" ]; then
+ printf "Required at least one parameter: schema version number, e.g. 7.0\n"
+ exit 1
+fi
+exp_version="$1"
+shift
+
+# Remaining arguments are used as mysql command line arguments
+
+# If the existing schema doesn't match, the fail
+VERSION=$(mysql_version "$@")
+if [ "$VERSION" = "" ]; then
+ printf "Cannot wipe data, schema version could not be detected.\n"
+ exit 1
+fi
+
+if [ "$VERSION" != "$exp_version" ]; then
+ printf 'Cannot wipe data, wrong schema version. '
+ printf 'Expected version %s, found %s.\n' "$exp_version" "$VERSION"
+ exit 1
+fi
+
+# Delete transient data from tables. Per MySQL documentation TRUNCATE
+# destroys and there recreates tables. As schema updates are typically
+# very slow, we're use deletes here. We turn off foreign key checks to
+# worrying about table order. We also set the session variable
+# disable_audit to turn off Back audit procedures, to avoid attempting
+# to create entries for deleted records.
+mysql "$@" <<EOF
+START TRANSACTION;
+SET SESSION FOREIGN_KEY_CHECKS = 0;
+SET @disable_audit = 1;
+
+DELETE FROM dhcp4_global_parameter;
+DELETE FROM dhcp4_global_parameter_server;
+DELETE FROM dhcp4_option_def;
+DELETE FROM dhcp4_option_def_server;
+DELETE FROM dhcp4_options;
+DELETE FROM dhcp4_options_server;
+DELETE FROM dhcp4_pool;
+# preserve special server "all"
+DELETE FROM dhcp4_server WHERE tag != "all";
+DELETE FROM dhcp4_shared_network;
+DELETE FROM dhcp4_shared_network_server;
+DELETE FROM dhcp4_subnet;
+DELETE FROM dhcp4_subnet_server;
+DELETE FROM dhcp4_audit_revision;
+DELETE FROM dhcp4_audit;
+DELETE FROM dhcp4_client_class;
+DELETE FROM dhcp4_client_class_order;
+DELETE FROM dhcp4_client_class_dependency;
+DELETE FROM dhcp4_client_class_server;
+DELETE FROM dhcp6_global_parameter;
+DELETE FROM dhcp6_global_parameter_server;
+DELETE FROM dhcp6_option_def;
+DELETE FROM dhcp6_option_def_server;
+DELETE FROM dhcp6_options;
+DELETE FROM dhcp6_options_server;
+DELETE FROM dhcp6_pd_pool;
+DELETE FROM dhcp6_pool;
+# preserve special server "all"
+DELETE FROM dhcp6_server WHERE tag != "all";
+DELETE FROM dhcp6_shared_network;
+DELETE FROM dhcp6_shared_network_server;
+DELETE FROM dhcp6_subnet;
+DELETE FROM dhcp6_subnet_server;
+DELETE FROM dhcp6_audit;
+DELETE FROM dhcp6_audit_revision;
+DELETE FROM dhcp6_client_class;
+DELETE FROM dhcp6_client_class_order;
+DELETE FROM dhcp6_client_class_dependency;
+DELETE FROM dhcp6_client_class_server;
+DELETE FROM hosts;
+DELETE FROM ipv6_reservations;
+DELETE FROM lease4;
+DELETE FROM lease4_stat;
+DELETE FROM lease6;
+DELETE FROM lease6_stat;
+DELETE FROM logs;
+DELETE FROM lease4_stat_by_client_class;
+DELETE FROM lease6_stat_by_client_class;
+COMMIT;
+EOF
diff --git a/src/share/database/scripts/pgsql/Makefile.am b/src/share/database/scripts/pgsql/Makefile.am
new file mode 100644
index 0000000..f9c2096
--- /dev/null
+++ b/src/share/database/scripts/pgsql/Makefile.am
@@ -0,0 +1,34 @@
+SUBDIRS = .
+
+pgsqldir = ${datarootdir}/${PACKAGE_NAME}/scripts/pgsql
+
+# non-executable
+pgsql_DATA =
+pgsql_DATA += dhcpdb_create.pgsql
+pgsql_DATA += dhcpdb_drop.pgsql
+
+# executable
+pgsql_SCRIPTS =
+pgsql_SCRIPTS += upgrade_001.0_to_002.0.sh
+pgsql_SCRIPTS += upgrade_002.0_to_003.0.sh
+pgsql_SCRIPTS += upgrade_003.0_to_003.1.sh
+pgsql_SCRIPTS += upgrade_003.1_to_003.2.sh
+pgsql_SCRIPTS += upgrade_003.2_to_003.3.sh
+pgsql_SCRIPTS += upgrade_003.3_to_004.0.sh
+pgsql_SCRIPTS += upgrade_004.0_to_005.0.sh
+pgsql_SCRIPTS += upgrade_005.0_to_005.1.sh
+pgsql_SCRIPTS += upgrade_005.1_to_006.0.sh
+pgsql_SCRIPTS += upgrade_006.0_to_006.1.sh
+pgsql_SCRIPTS += upgrade_006.1_to_006.2.sh
+pgsql_SCRIPTS += upgrade_006.2_to_007.0.sh
+pgsql_SCRIPTS += upgrade_007_to_008.sh
+pgsql_SCRIPTS += upgrade_008_to_009.sh
+pgsql_SCRIPTS += upgrade_009_to_010.sh
+pgsql_SCRIPTS += upgrade_010_to_011.sh
+pgsql_SCRIPTS += upgrade_011_to_012.sh
+pgsql_SCRIPTS += upgrade_012_to_013.sh
+pgsql_SCRIPTS += wipe_data.sh
+
+DISTCLEANFILES = ${pgsql_SCRIPTS}
+
+EXTRA_DIST = ${pgsql_DATA}
diff --git a/src/share/database/scripts/pgsql/Makefile.in b/src/share/database/scripts/pgsql/Makefile.in
new file mode 100644
index 0000000..781bb42
--- /dev/null
+++ b/src/share/database/scripts/pgsql/Makefile.in
@@ -0,0 +1,880 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/share/database/scripts/pgsql
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = upgrade_001.0_to_002.0.sh \
+ upgrade_002.0_to_003.0.sh upgrade_003.0_to_003.1.sh \
+ upgrade_003.1_to_003.2.sh upgrade_003.2_to_003.3.sh \
+ upgrade_003.3_to_004.0.sh upgrade_004.0_to_005.0.sh \
+ upgrade_005.0_to_005.1.sh upgrade_005.1_to_006.0.sh \
+ upgrade_006.0_to_006.1.sh upgrade_006.1_to_006.2.sh \
+ upgrade_006.2_to_007.0.sh upgrade_007_to_008.sh \
+ upgrade_008_to_009.sh upgrade_009_to_010.sh \
+ upgrade_010_to_011.sh upgrade_011_to_012.sh \
+ upgrade_012_to_013.sh wipe_data.sh
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pgsqldir)" "$(DESTDIR)$(pgsqldir)"
+SCRIPTS = $(pgsql_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+DATA = $(pgsql_DATA)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/upgrade_001.0_to_002.0.sh.in \
+ $(srcdir)/upgrade_002.0_to_003.0.sh.in \
+ $(srcdir)/upgrade_003.0_to_003.1.sh.in \
+ $(srcdir)/upgrade_003.1_to_003.2.sh.in \
+ $(srcdir)/upgrade_003.2_to_003.3.sh.in \
+ $(srcdir)/upgrade_003.3_to_004.0.sh.in \
+ $(srcdir)/upgrade_004.0_to_005.0.sh.in \
+ $(srcdir)/upgrade_005.0_to_005.1.sh.in \
+ $(srcdir)/upgrade_005.1_to_006.0.sh.in \
+ $(srcdir)/upgrade_006.0_to_006.1.sh.in \
+ $(srcdir)/upgrade_006.1_to_006.2.sh.in \
+ $(srcdir)/upgrade_006.2_to_007.0.sh.in \
+ $(srcdir)/upgrade_007_to_008.sh.in \
+ $(srcdir)/upgrade_008_to_009.sh.in \
+ $(srcdir)/upgrade_009_to_010.sh.in \
+ $(srcdir)/upgrade_010_to_011.sh.in \
+ $(srcdir)/upgrade_011_to_012.sh.in \
+ $(srcdir)/upgrade_012_to_013.sh.in $(srcdir)/wipe_data.sh.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+pgsqldir = ${datarootdir}/${PACKAGE_NAME}/scripts/pgsql
+
+# non-executable
+pgsql_DATA = dhcpdb_create.pgsql dhcpdb_drop.pgsql
+
+# executable
+pgsql_SCRIPTS = upgrade_001.0_to_002.0.sh upgrade_002.0_to_003.0.sh \
+ upgrade_003.0_to_003.1.sh upgrade_003.1_to_003.2.sh \
+ upgrade_003.2_to_003.3.sh upgrade_003.3_to_004.0.sh \
+ upgrade_004.0_to_005.0.sh upgrade_005.0_to_005.1.sh \
+ upgrade_005.1_to_006.0.sh upgrade_006.0_to_006.1.sh \
+ upgrade_006.1_to_006.2.sh upgrade_006.2_to_007.0.sh \
+ upgrade_007_to_008.sh upgrade_008_to_009.sh \
+ upgrade_009_to_010.sh upgrade_010_to_011.sh \
+ upgrade_011_to_012.sh upgrade_012_to_013.sh wipe_data.sh
+DISTCLEANFILES = ${pgsql_SCRIPTS}
+EXTRA_DIST = ${pgsql_DATA}
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/share/database/scripts/pgsql/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/share/database/scripts/pgsql/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+upgrade_001.0_to_002.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_001.0_to_002.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_002.0_to_003.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_002.0_to_003.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_003.0_to_003.1.sh: $(top_builddir)/config.status $(srcdir)/upgrade_003.0_to_003.1.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_003.1_to_003.2.sh: $(top_builddir)/config.status $(srcdir)/upgrade_003.1_to_003.2.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_003.2_to_003.3.sh: $(top_builddir)/config.status $(srcdir)/upgrade_003.2_to_003.3.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_003.3_to_004.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_003.3_to_004.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_004.0_to_005.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_004.0_to_005.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_005.0_to_005.1.sh: $(top_builddir)/config.status $(srcdir)/upgrade_005.0_to_005.1.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_005.1_to_006.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_005.1_to_006.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_006.0_to_006.1.sh: $(top_builddir)/config.status $(srcdir)/upgrade_006.0_to_006.1.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_006.1_to_006.2.sh: $(top_builddir)/config.status $(srcdir)/upgrade_006.1_to_006.2.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_006.2_to_007.0.sh: $(top_builddir)/config.status $(srcdir)/upgrade_006.2_to_007.0.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_007_to_008.sh: $(top_builddir)/config.status $(srcdir)/upgrade_007_to_008.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_008_to_009.sh: $(top_builddir)/config.status $(srcdir)/upgrade_008_to_009.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_009_to_010.sh: $(top_builddir)/config.status $(srcdir)/upgrade_009_to_010.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_010_to_011.sh: $(top_builddir)/config.status $(srcdir)/upgrade_010_to_011.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_011_to_012.sh: $(top_builddir)/config.status $(srcdir)/upgrade_011_to_012.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+upgrade_012_to_013.sh: $(top_builddir)/config.status $(srcdir)/upgrade_012_to_013.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+wipe_data.sh: $(top_builddir)/config.status $(srcdir)/wipe_data.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+install-pgsqlSCRIPTS: $(pgsql_SCRIPTS)
+ @$(NORMAL_INSTALL)
+ @list='$(pgsql_SCRIPTS)'; test -n "$(pgsqldir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pgsqldir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pgsqldir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n' \
+ -e 'h;s|.*|.|' \
+ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+ if (++n[d] == $(am__install_max)) { \
+ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+ else { print "f", d "/" $$4, $$1 } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(pgsqldir)$$dir'"; \
+ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(pgsqldir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-pgsqlSCRIPTS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pgsql_SCRIPTS)'; test -n "$(pgsqldir)" || exit 0; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 's,.*/,,;$(transform)'`; \
+ dir='$(DESTDIR)$(pgsqldir)'; $(am__uninstall_files_from_dir)
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pgsqlDATA: $(pgsql_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(pgsql_DATA)'; test -n "$(pgsqldir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pgsqldir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pgsqldir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pgsqldir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(pgsqldir)" || exit $$?; \
+ done
+
+uninstall-pgsqlDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pgsql_DATA)'; test -n "$(pgsqldir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pgsqldir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(SCRIPTS) $(DATA)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(pgsqldir)" "$(DESTDIR)$(pgsqldir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-pgsqlDATA install-pgsqlSCRIPTS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-pgsqlDATA uninstall-pgsqlSCRIPTS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-pgsqlDATA \
+ install-pgsqlSCRIPTS install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-pgsqlDATA \
+ uninstall-pgsqlSCRIPTS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/share/database/scripts/pgsql/dhcpdb_create.pgsql b/src/share/database/scripts/pgsql/dhcpdb_create.pgsql
new file mode 100644
index 0000000..f24e086
--- /dev/null
+++ b/src/share/database/scripts/pgsql/dhcpdb_create.pgsql
@@ -0,0 +1,5658 @@
+-- Copyright (C) 2012-2022 Internet Systems Consortium, Inc. ("ISC")
+
+-- This Source Code Form 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/.
+
+-- This is the Kea DHCP schema specification for PostgreSQL.
+
+-- The schema is reasonably portable (with the exception of some field types
+-- specification, which are PostgreSQL-specific). Minor changes might be needed
+-- for other databases.
+
+-- To create the schema, either type the command:
+
+-- psql -U <user> -W <password> <database> < dhcpdb_create.pgsql
+
+-- ... at the command prompt, or log in to the PostgreSQL database and at the "postgres=#"
+-- prompt, issue the command:
+
+-- @dhcpdb_create.pgsql
+
+-- Start a single transaction for the Entire script
+START TRANSACTION;
+
+-- Holds the IPv4 leases.
+CREATE TABLE lease4 (
+ address BIGINT PRIMARY KEY NOT NULL, -- IPv4 address
+ hwaddr BYTEA, -- Hardware address
+ client_id BYTEA, -- Client ID
+ valid_lifetime BIGINT, -- Length of the lease (seconds)
+ expire TIMESTAMP WITH TIME ZONE, -- Expiration time of the lease
+ subnet_id BIGINT, -- Subnet identification
+ fqdn_fwd BOOLEAN, -- Has forward DNS update been performed by a server
+ fqdn_rev BOOLEAN, -- Has reverse DNS update been performed by a server
+ hostname VARCHAR(255) -- The FQDN of the client
+ );
+
+-- Create search indexes for lease4 table
+-- index by hwaddr and subnet_id
+CREATE INDEX lease4_by_hwaddr_subnet_id ON lease4 (hwaddr, subnet_id);
+
+-- index by client_id and subnet_id
+CREATE INDEX lease4_by_client_id_subnet_id ON lease4 (client_id, subnet_id);
+
+-- Holds the IPv6 leases.
+-- N.B. The use of a VARCHAR for the address is temporary for development:
+-- it will eventually be replaced by BINARY(16).
+CREATE TABLE lease6 (
+ address VARCHAR(39) PRIMARY KEY NOT NULL, -- IPv6 address
+ duid BYTEA, -- DUID
+ valid_lifetime BIGINT, -- Length of the lease (seconds)
+ expire TIMESTAMP WITH TIME ZONE, -- Expiration time of the lease
+ subnet_id BIGINT, -- Subnet identification
+ pref_lifetime BIGINT, -- Preferred lifetime
+ lease_type SMALLINT, -- Lease type (see lease6_types
+ -- table for possible values)
+ iaid INT, -- See Section 12 of RFC 8415
+ prefix_len SMALLINT, -- For IA_PD only
+ fqdn_fwd BOOLEAN, -- Has forward DNS update been performed by a server
+ fqdn_rev BOOLEAN, -- Has reverse DNS update been performed by a server
+ hostname VARCHAR(255) -- The FQDN of the client
+ );
+
+-- Create search indexes for lease6 table
+-- index by iaid, subnet_id, and duid
+CREATE INDEX lease6_by_iaid_subnet_id_duid ON lease6 (iaid, subnet_id, duid);
+
+-- ... and a definition of lease6 types. This table is a convenience for
+-- users of the database - if they want to view the lease table and use the
+-- type names, they can join this table with the lease6 table
+CREATE TABLE lease6_types (
+ lease_type SMALLINT PRIMARY KEY NOT NULL, -- Lease type code.
+ name VARCHAR(5) -- Name of the lease type
+ );
+
+INSERT INTO lease6_types VALUES (0, 'IA_NA'); -- Non-temporary v6 addresses
+INSERT INTO lease6_types VALUES (1, 'IA_TA'); -- Temporary v6 addresses
+INSERT INTO lease6_types VALUES (2, 'IA_PD'); -- Prefix delegations
+
+-- Finally, the version of the schema. We start at 0.1 during development.
+-- This table is only modified during schema upgrades. For historical reasons
+-- (related to the names of the columns in the BIND 10 DNS database file), the
+-- first column is called "version" and not "major".
+CREATE TABLE schema_version (
+ version INT PRIMARY KEY NOT NULL, -- Major version number
+ minor INT -- Minor version number
+ );
+
+INSERT INTO schema_version VALUES (1, 0);
+
+--
+-- Schema 2.0 specification starts here.
+--
+
+-- Add state column to the lease4 table.
+ALTER TABLE lease4
+ ADD COLUMN state INT8 DEFAULT 0;
+
+-- Add state column to the lease6 table.
+ALTER TABLE lease6
+ ADD COLUMN state INT8 DEFAULT 0;
+
+-- Create indexes for querying leases in a given state and segregated
+-- by the expiration time. One of the applications is to retrieve all
+-- expired leases. However, these indexes can be also used to retrieve
+-- leases in a given state regardless of the expiration time.
+CREATE INDEX lease4_by_state_expire ON lease4 (state ASC, expire ASC);
+CREATE INDEX lease6_by_state_expire ON lease6 (state ASC, expire ASC);
+
+-- Create table holding mapping of the lease states to their names.
+-- This is not used in queries from the DHCP server but rather in
+-- direct queries from the lease database management tools.
+CREATE TABLE lease_state (
+ state INT8 PRIMARY KEY NOT NULL,
+ name VARCHAR(64) NOT NULL);
+
+-- Insert currently defined state names.
+INSERT INTO lease_state VALUES (0, 'default');
+INSERT INTO lease_state VALUES (1, 'declined');
+INSERT INTO lease_state VALUES (2, 'expired-reclaimed');
+
+-- Add a constraint that any state value added to the lease4 must
+-- map to a value in the lease_state table.
+ALTER TABLE lease4
+ ADD CONSTRAINT fk_lease4_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+-- Add a constraint that any state value added to the lease6 must
+-- map to a value in the lease_state table.
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+-- Add a constraint that lease type in the lease6 table must map
+-- to a lease type defined in the lease6_types table.
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_type FOREIGN KEY (lease_type)
+ REFERENCES lease6_types (lease_type);
+
+--
+-- FUNCTION that returns a result set containing the column names for lease4 dumps.
+DROP FUNCTION IF EXISTS lease4DumpHeader();
+CREATE OR REPLACE FUNCTION lease4DumpHeader() RETURNS text AS $$
+ select cast('address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state' as text) as result;
+$$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the data for lease4 dumps.
+DROP FUNCTION IF EXISTS lease4DumpData();
+CREATE OR REPLACE FUNCTION lease4DumpData() RETURNS
+ table (address inet,
+ hwaddr text,
+ client_id text,
+ valid_lifetime bigint,
+ expire TIMESTAMP WITH TIME ZONE,
+ subnet_id bigint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text
+ ) as $$
+ SELECT ('0.0.0.0'::inet + l.address),
+ encode(l.hwaddr,'hex'),
+ encode(l.client_id,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name
+ FROM lease4 l
+ left outer join lease_state s on (l.state = s.state);
+$$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the column names for lease6 dumps.
+DROP FUNCTION IF EXISTS lease6DumpHeader();
+CREATE OR REPLACE FUNCTION lease6DumpHeader() RETURNS text AS $$
+ select cast('address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,state' as text) as result;
+$$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the data for lease6 dumps.
+DROP FUNCTION IF EXISTS lease6DumpData();
+CREATE OR REPLACE FUNCTION lease6DumpData() RETURNS
+ TABLE (
+ address text,
+ duid text,
+ valid_lifetime bigint,
+ expire TIMESTAMP WITH TIME ZONE,
+ subnet_id bigint,
+ pref_lifetime bigint,
+ name text,
+ iaid integer,
+ prefix_len smallint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text
+ ) AS $$
+ SELECT (l.address,
+ encode(l.duid,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ t.name,
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name)
+ FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state);
+$$ LANGUAGE SQL;
+--
+
+-- Set 2.0 schema version.
+UPDATE schema_version
+ SET version = '2', minor = '0';
+
+-- Schema 2.0 specification ends here.
+
+-- Upgrade to schema 3.0 begins here:
+
+--
+-- Table structure for table host_identifier_type.
+--
+
+CREATE TABLE host_identifier_type (
+ type SMALLINT PRIMARY KEY NOT NULL,
+ name VARCHAR(32) DEFAULT NULL
+);
+
+INSERT INTO host_identifier_type VALUES (0, 'hw-address');
+INSERT INTO host_identifier_type VALUES (1, 'duid');
+INSERT INTO host_identifier_type VALUES (2, 'circuit-id');
+INSERT INTO host_identifier_type VALUES (3, 'client-id');
+
+--
+-- Table structure for table dhcp_option_scope.
+--
+
+CREATE TABLE dhcp_option_scope (
+ scope_id SMALLINT PRIMARY KEY NOT NULL,
+ scope_name VARCHAR(32) DEFAULT NULL
+);
+
+INSERT INTO dhcp_option_scope VALUES (0, 'global');
+INSERT INTO dhcp_option_scope VALUES (1, 'subnet');
+INSERT INTO dhcp_option_scope VALUES (2, 'client-class');
+INSERT INTO dhcp_option_scope VALUES (3, 'host');
+
+--
+-- Table structure for table hosts.
+--
+-- Primary key and unique constraints automatically create indexes,
+-- foreign key constraints do not.
+CREATE TABLE hosts (
+ host_id SERIAL PRIMARY KEY NOT NULL,
+ dhcp_identifier BYTEA NOT NULL,
+ dhcp_identifier_type SMALLINT NOT NULL,
+ dhcp4_subnet_id INT DEFAULT NULL,
+ dhcp6_subnet_id INT DEFAULT NULL,
+ ipv4_address BIGINT DEFAULT NULL,
+ hostname VARCHAR(255) DEFAULT NULL,
+ dhcp4_client_classes VARCHAR(255) DEFAULT NULL,
+ dhcp6_client_classes VARCHAR(255) DEFAULT NULL,
+ CONSTRAINT key_dhcp4_ipv4_address_subnet_id UNIQUE (ipv4_address, dhcp4_subnet_id),
+ CONSTRAINT key_dhcp4_identifier_subnet_id UNIQUE (dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id),
+ CONSTRAINT key_dhcp6_identifier_subnet_id UNIQUE (dhcp_identifier, dhcp_identifier_type, dhcp6_subnet_id),
+ CONSTRAINT fk_host_identifier_type FOREIGN KEY (dhcp_identifier_type) REFERENCES host_identifier_type (type)
+ ON DELETE CASCADE
+);
+
+CREATE INDEX fk_host_identifier_type ON hosts (dhcp_identifier_type);
+
+--
+-- Table structure for table dhcp4_options.
+--
+
+CREATE TABLE dhcp4_options (
+ option_id SERIAL PRIMARY KEY NOT NULL,
+ code SMALLINT NOT NULL,
+ value BYTEA,
+ formatted_value TEXT,
+ space VARCHAR(128) DEFAULT NULL,
+ persistent BOOLEAN NOT NULL DEFAULT 'f',
+ dhcp_client_class VARCHAR(128) DEFAULT NULL,
+ dhcp4_subnet_id BIGINT DEFAULT NULL,
+ host_id INT DEFAULT NULL,
+ scope_id SMALLINT NOT NULL,
+ CONSTRAINT fk_options_host1 FOREIGN KEY (host_id) REFERENCES hosts (host_id) ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp4_option_scode FOREIGN KEY (scope_id) REFERENCES dhcp_option_scope (scope_id) ON DELETE CASCADE
+);
+
+CREATE INDEX fk_dhcp4_options_host1_idx ON dhcp4_options (host_id);
+CREATE INDEX fk_dhcp4_options_scope_idx ON dhcp4_options (scope_id);
+
+--
+-- Table structure for table dhcp6_options.
+--
+
+CREATE TABLE dhcp6_options (
+ option_id SERIAL PRIMARY KEY NOT NULL,
+ code INT NOT NULL,
+ value BYTEA,
+ formatted_value TEXT,
+ space VARCHAR(128) DEFAULT NULL,
+ persistent BOOLEAN NOT NULL DEFAULT 'f',
+ dhcp_client_class VARCHAR(128) DEFAULT NULL,
+ dhcp6_subnet_id BIGINT DEFAULT NULL,
+ host_id INT DEFAULT NULL,
+ scope_id SMALLINT NOT NULL,
+ CONSTRAINT fk_options_host10 FOREIGN KEY (host_id) REFERENCES hosts (host_id) ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp6_option_scode FOREIGN KEY (scope_id) REFERENCES dhcp_option_scope (scope_id) ON DELETE CASCADE
+);
+
+CREATE INDEX fk_dhcp6_options_host1_idx ON dhcp6_options (host_id);
+CREATE INDEX fk_dhcp6_options_scope_idx ON dhcp6_options (scope_id);
+
+--
+-- Table structure for table ipv6_reservations.
+--
+
+CREATE TABLE ipv6_reservations (
+ reservation_id SERIAL PRIMARY KEY NOT NULL,
+ address VARCHAR(39) NOT NULL,
+ prefix_len SMALLINT NOT NULL DEFAULT '128',
+ type SMALLINT NOT NULL DEFAULT '0',
+ dhcp6_iaid INT DEFAULT NULL,
+ host_id INT NOT NULL,
+ CONSTRAINT key_dhcp6_address_prefix_len UNIQUE (address, prefix_len),
+ CONSTRAINT fk_ipv6_reservations_host FOREIGN KEY (host_id) REFERENCES hosts (host_id) ON DELETE CASCADE
+);
+
+CREATE INDEX fk_ipv6_reservations_host_idx ON ipv6_reservations (host_id);
+
+--
+-- Table structure for table lease_hwaddr_source.
+--
+
+CREATE TABLE lease_hwaddr_source (
+ hwaddr_source INT PRIMARY KEY NOT NULL,
+ name VARCHAR(40) DEFAULT NULL
+);
+
+-- In the event hardware address cannot be determined, we need to satisfy
+-- foreign key constraint between lease6 and lease_hardware_source.
+INSERT INTO lease_hwaddr_source VALUES (0, 'HWADDR_SOURCE_UNKNOWN');
+
+-- Hardware address obtained from raw sockets.
+INSERT INTO lease_hwaddr_source VALUES (1, 'HWADDR_SOURCE_RAW');
+
+-- Hardware address converted from IPv6 link-local address with EUI-64.
+INSERT INTO lease_hwaddr_source VALUES (2, 'HWADDR_SOURCE_IPV6_LINK_LOCAL');
+
+-- Hardware address extracted from client-id (duid).
+INSERT INTO lease_hwaddr_source VALUES (4, 'HWADDR_SOURCE_DUID');
+
+-- Hardware address extracted from client address relay option (RFC6939).
+INSERT INTO lease_hwaddr_source VALUES (8, 'HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION');
+
+-- Hardware address extracted from remote-id option (RFC4649).
+INSERT INTO lease_hwaddr_source VALUES (16, 'HWADDR_SOURCE_REMOTE_ID');
+
+-- Hardware address extracted from subscriber-id option (RFC4580).
+INSERT INTO lease_hwaddr_source VALUES (32, 'HWADDR_SOURCE_SUBSCRIBER_ID');
+
+-- Hardware address extracted from docsis options.
+INSERT INTO lease_hwaddr_source VALUES (64, 'HWADDR_SOURCE_DOCSIS_CMTS');
+
+INSERT INTO lease_hwaddr_source VALUES (128, 'HWADDR_SOURCE_DOCSIS_MODEM');
+
+-- Adding ORDER BY clause to sort by lease address.
+--
+-- FUNCTION that returns a result set containing the data for lease4 dumps.
+DROP FUNCTION IF EXISTS lease4DumpData();
+CREATE OR REPLACE FUNCTION lease4DumpData() RETURNS
+ table (address inet,
+ hwaddr text,
+ client_id text,
+ valid_lifetime bigint,
+ expire TIMESTAMP WITH TIME ZONE,
+ subnet_id bigint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text
+ ) as $$
+ SELECT ('0.0.0.0'::inet + l.address),
+ encode(l.hwaddr,'hex'),
+ encode(l.client_id,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name
+ FROM lease4 l
+ left outer join lease_state s on (l.state = s.state)
+ ORDER BY l.address;
+$$ LANGUAGE SQL;
+--
+
+-- Add new columns to lease6.
+ALTER TABLE lease6
+ ADD COLUMN hwaddr BYTEA DEFAULT NULL,
+ ADD COLUMN hwtype SMALLINT DEFAULT NULL,
+ ADD COLUMN hwaddr_source SMALLINT DEFAULT NULL;
+
+--
+-- FUNCTION that returns a result set containing the column names for lease6 dumps.
+DROP FUNCTION IF EXISTS lease6DumpHeader();
+CREATE OR REPLACE FUNCTION lease6DumpHeader() RETURNS text AS $$
+ select cast('address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,state,hwaddr,hwtype,hwaddr_source' as text) as result;
+$$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the data for lease6 dumps.
+DROP FUNCTION IF EXISTS lease6DumpData();
+CREATE OR REPLACE FUNCTION lease6DumpData() RETURNS
+ TABLE (
+ address text,
+ duid text,
+ valid_lifetime bigint,
+ expire TIMESTAMP WITH TIME ZONE,
+ subnet_id bigint,
+ pref_lifetime bigint,
+ name text,
+ iaid integer,
+ prefix_len smallint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text,
+ hwaddr text,
+ hwtype smallint,
+ hwaddr_source text
+ ) AS $$
+ SELECT (l.address,
+ encode(l.duid,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ t.name,
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name,
+ encode(l.hwaddr,'hex'),
+ l.hwtype,
+ h.name
+ )
+ FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state)
+ left outer join lease_hwaddr_source h on (l.hwaddr_source = h.hwaddr_source)
+ ORDER BY l.address;
+$$ LANGUAGE SQL;
+
+-- Add columns holding reservations for siaddr, sname and file fields
+-- carried within DHCPv4 message.
+ALTER TABLE hosts ADD COLUMN dhcp4_next_server BIGINT DEFAULT NULL;
+ALTER TABLE hosts ADD COLUMN dhcp4_server_hostname VARCHAR(64) DEFAULT NULL;
+ALTER TABLE hosts ADD COLUMN dhcp4_boot_file_name VARCHAR(128) DEFAULT NULL;
+
+-- Set 3.0 schema version.
+UPDATE schema_version
+ SET version = '3', minor = '0';
+
+-- Schema 3.0 specification ends here.
+
+-- Upgrade to schema 3.1 begins here:
+
+-- This is a placeholder for the changes between 3.0 and 3.1. We have added a
+-- missing 'client-id' host reservation type entry that had been accidentally
+-- omitted when the 2.0 -> 3.0 upgrade script was created.
+-- Also, new flexible identifier has been added.
+INSERT INTO host_identifier_type VALUES (4, 'flex-id');
+
+-- Set 3.1 schema version.
+UPDATE schema_version
+ SET version = '3', minor = '1';
+
+-- Schema 3.1 specification ends here.
+
+-- Upgrade to schema 3.2 begins here:
+
+-- Remove constraints which perform too restrictive checks on the inserted
+-- host reservations. We want to be able to insert host reservations which
+-- include no specific IPv4 address or those that have repeating subnet
+-- identifiers, e.g. IPv4 reservations would typically include 0 (or null)
+-- IPv6 subnet identifiers.
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp4_ipv4_address_subnet_id;
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp4_identifier_subnet_id;
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp6_identifier_subnet_id;
+
+-- Create partial indexes instead of the constraints that we have removed.
+
+-- IPv4 address/IPv4 subnet identifier pair is unique if subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX key_dhcp4_ipv4_address_subnet_id ON hosts
+ (ipv4_address ASC, dhcp4_subnet_id ASC)
+ WHERE ipv4_address IS NOT NULL AND ipv4_address <> 0;
+
+-- Client identifier is unique within an IPv4 subnet when subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX key_dhcp4_identifier_subnet_id ON hosts
+ (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp4_subnet_id ASC)
+ WHERE (dhcp4_subnet_id IS NOT NULL AND dhcp4_subnet_id <> 0);
+
+-- Client identifier is unique within an IPv6 subnet when subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX key_dhcp6_identifier_subnet_id ON hosts
+ (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp6_subnet_id ASC)
+ WHERE (dhcp6_subnet_id IS NOT NULL AND dhcp6_subnet_id <> 0);
+
+-- Set 3.2 schema version.
+UPDATE schema_version
+ SET version = '3', minor = '2';
+
+-- Schema 3.2 specification ends here.
+
+-- Upgrade to schema 3.3 begins here:
+
+-- Change subnet ID columns type to BIGINT to match lease4/6 tables
+ALTER TABLE hosts ALTER COLUMN dhcp4_subnet_id TYPE BIGINT;
+ALTER TABLE hosts ALTER COLUMN dhcp6_subnet_id TYPE BIGINT;
+
+ALTER TABLE dhcp4_options ALTER COLUMN dhcp4_subnet_id TYPE BIGINT;
+ALTER TABLE dhcp6_options ALTER COLUMN dhcp6_subnet_id TYPE BIGINT;
+
+-- Set 3.3 schema version.
+UPDATE schema_version
+ SET version = '3', minor = '3';
+
+-- Schema 3.3 specification ends here.
+
+-- Upgrade to schema 4.0 begins here:
+
+-- Add a column holding hosts for user context.
+ALTER TABLE hosts ADD COLUMN user_context TEXT;
+
+-- Add a column holding DHCP options for user context.
+ALTER TABLE dhcp4_options ADD COLUMN user_context TEXT;
+ALTER TABLE dhcp6_options ADD COLUMN user_context TEXT;
+
+-- Create index for searching leases by subnet identifier.
+CREATE INDEX lease4_by_subnet_id ON lease4 (subnet_id);
+
+-- Create for searching leases by subnet identifier and lease type.
+CREATE INDEX lease6_by_subnet_id_lease_type ON lease6 (subnet_id, lease_type);
+
+-- The index by iaid_subnet_id_duid is not the best choice because there are
+-- cases when we don't specify subnet identifier while searching leases. The
+-- index will be universal if the subnet_id is the right most column in the
+-- index.
+DROP INDEX lease6_by_iaid_subnet_id_duid;
+CREATE INDEX lease6_by_duid_iaid_subnet_id ON lease6 (duid, iaid, subnet_id);
+
+-- Create v4 lease statistics table
+CREATE TABLE lease4_stat (
+ subnet_id BIGINT NOT NULL,
+ state INT8 NOT NULL,
+ leases BIGINT,
+ PRIMARY KEY (subnet_id, state)
+);
+
+--
+-- Create v4 insert trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease4_insert() RETURNS trigger AS $stat_lease4_insert$
+BEGIN
+ IF NEW.state < 2 THEN
+ UPDATE lease4_stat
+ SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat VALUES (new.subnet_id, new.state, 1);
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+$stat_lease4_insert$ LANGUAGE plpgsql;
+
+-- Create v4 insert trigger procedure
+CREATE TRIGGER stat_lease4_insert
+AFTER INSERT ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease4_insert();
+
+--
+-- Create v4 update trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease4_update() RETURNS trigger AS $stat_lease4_update$
+BEGIN
+ IF OLD.state != NEW.state THEN
+ IF OLD.state < 2 THEN
+ -- Decrement the old state count if record exists
+ UPDATE lease4_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND state = OLD.state;
+ END IF;
+
+ IF NEW.state < 2 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ -- Insert new state record if it does not exist
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat VALUES (NEW.subnet_id, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+$stat_lease4_update$ LANGUAGE plpgsql;
+
+-- Create v4 update trigger
+CREATE TRIGGER stat_lease4_update
+AFTER UPDATE ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease4_update();
+
+
+--
+-- Create the v4 delete trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease4_delete() RETURNS trigger AS $stat_lease4_delete$
+BEGIN
+ IF OLD.state < 2 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease4_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND OLD.state = state;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+$stat_lease4_delete$ LANGUAGE plpgsql;
+
+-- Create the v4 delete trigger
+CREATE TRIGGER stat_lease4_delete
+AFTER DELETE ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease4_delete();
+
+-- Create v6 lease statistics table
+CREATE TABLE lease6_stat (
+ subnet_id BIGINT NOT NULL,
+ lease_type SMALLINT NOT NULL,
+ state INT8 NOT NULL,
+ leases BIGINT,
+ PRIMARY KEY (subnet_id, lease_type, state)
+);
+
+--
+-- Create v6 insert trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease6_insert() RETURNS trigger AS $stat_lease6_insert$
+BEGIN
+ IF NEW.state < 2 THEN
+ UPDATE lease6_stat
+ SET leases = leases + 1
+ WHERE
+ subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat
+ VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+$stat_lease6_insert$ LANGUAGE plpgsql;
+
+-- Create v6 insert trigger procedure
+CREATE TRIGGER stat_lease6_insert
+AFTER INSERT ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease6_insert();
+
+--
+-- Create v6 update trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease6_update() RETURNS trigger AS $stat_lease6_update$
+BEGIN
+ IF OLD.state != NEW.state THEN
+ IF OLD.state < 2 THEN
+ -- Decrement the old state count if record exists
+ UPDATE lease6_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+
+ IF NEW.state < 2 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ -- Insert new state record if it does not exist
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+$stat_lease6_update$ LANGUAGE plpgsql;
+
+-- Create v6 update trigger
+CREATE TRIGGER stat_lease6_update
+AFTER UPDATE ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease6_update();
+
+--
+-- Create the v6 delete trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease6_delete() RETURNS trigger AS $stat_lease6_delete$
+BEGIN
+ IF OLD.state < 2 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease6_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND OLD.state = state;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+$stat_lease6_delete$ LANGUAGE plpgsql;
+
+-- Create the v6 delete trigger
+CREATE TRIGGER stat_lease6_delete
+AFTER DELETE ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease6_delete();
+
+-- Set 4.0 schema version.
+UPDATE schema_version
+ SET version = '4', minor = '0';
+
+-- Schema 4.0 specification ends here.
+
+-- Upgrade to schema 5.0 begins here:
+
+-- Add a column holding leases for user context.
+ALTER TABLE lease4 ADD COLUMN user_context TEXT;
+ALTER TABLE lease6 ADD COLUMN user_context TEXT;
+
+--
+DROP FUNCTION IF EXISTS lease4DumpHeader();
+CREATE OR REPLACE FUNCTION lease4DumpHeader() RETURNS text AS $$
+ select cast('address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context' as text) as result;
+$$ LANGUAGE SQL;
+--
+
+--
+DROP FUNCTION IF EXISTS lease4DumpData();
+CREATE OR REPLACE FUNCTION lease4DumpData() RETURNS
+ table (address inet,
+ hwaddr text,
+ client_id text,
+ valid_lifetime bigint,
+ expire TIMESTAMP WITH TIME ZONE,
+ subnet_id bigint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text,
+ user_context text
+ ) as $$
+ SELECT ('0.0.0.0'::inet + l.address),
+ encode(l.hwaddr,'hex'),
+ encode(l.client_id,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name,
+ l.user_context
+ FROM lease4 l
+ left outer join lease_state s on (l.state = s.state)
+ ORDER BY l.address;
+$$ LANGUAGE SQL;
+--
+
+--
+DROP FUNCTION IF EXISTS lease6DumpHeader();
+CREATE OR REPLACE FUNCTION lease6DumpHeader() RETURNS text AS $$
+ select cast('address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,state,hwaddr,hwtype,hwaddr_source,user_context' as text) as result;
+$$ LANGUAGE SQL;
+--
+
+--
+DROP FUNCTION IF EXISTS lease6DumpData();
+CREATE OR REPLACE FUNCTION lease6DumpData() RETURNS
+ TABLE (
+ address text,
+ duid text,
+ valid_lifetime bigint,
+ expire TIMESTAMP WITH TIME ZONE,
+ subnet_id bigint,
+ pref_lifetime bigint,
+ name text,
+ iaid integer,
+ prefix_len smallint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text,
+ hwaddr text,
+ hwtype smallint,
+ hwaddr_source text,
+ user_context text
+ ) AS $$
+ SELECT (l.address,
+ encode(l.duid,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ t.name,
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name,
+ encode(l.hwaddr,'hex'),
+ l.hwtype,
+ h.name,
+ l.user_context
+
+ )
+ FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state)
+ left outer join lease_hwaddr_source h on (l.hwaddr_source = h.hwaddr_source)
+ ORDER BY l.address;
+$$ LANGUAGE SQL;
+--
+
+-- Create logs table (logs table is used by forensic logging hook library)
+CREATE TABLE logs (
+ timestamp TIMESTAMP WITH TIME ZONE
+ DEFAULT CURRENT_TIMESTAMP, -- creation timestamp
+ address VARCHAR(43) NULL, -- address or prefix
+ log TEXT NOT NULL -- the log itself
+ );
+
+-- Create search indexes
+CREATE INDEX timestamp_id ON logs (timestamp);
+CREATE INDEX address_id ON logs (address);
+
+-- Create auth_key in hosts table for storing keys for DHCPv6 reconfigure.
+ALTER TABLE hosts ADD COLUMN auth_key VARCHAR(16) DEFAULT NULL;
+
+-- Set schema 5.0 version
+UPDATE schema_version
+ SET version = '5', minor = '0';
+
+-- Schema 5.0 specification ends here.
+
+-- Upgrade to schema 5.1 begins here:
+
+-- Put the auth key in hexadecimal (double size but far more user friendly).
+ALTER TABLE hosts ALTER COLUMN auth_key TYPE VARCHAR(32);
+
+-- Set schema 5.1 version
+UPDATE schema_version
+ SET version = '5', minor = '1';
+
+-- Schema 5.1 specification ends here.
+
+-- Upgrade to schema 6.0 begins here:
+
+-- Create a lower case hostname index for hosts.
+CREATE INDEX hosts_by_hostname ON hosts (lower(hostname))
+WHERE hostname IS NOT NULL;
+
+-- Create a hostname index for lease4.
+CREATE INDEX lease4_by_hostname ON lease4 (lower(hostname))
+WHERE hostname IS NOT NULL;
+
+-- Create a hostname index for lease6.
+CREATE INDEX lease6_by_hostname ON lease6 (lower(hostname))
+WHERE hostname IS NOT NULL;
+
+-- Set 6.0 schema version.
+UPDATE schema_version
+ SET version = '6', minor = '0';
+
+-- Schema 6.0 specification ends here.
+
+-- Upgrade to schema 6.1 begins here:
+
+-- Fix v4 update trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease4_update() RETURNS trigger AS $stat_lease4_update$
+BEGIN
+ IF OLD.subnet_id != NEW.subnet_id OR OLD.state != NEW.state THEN
+ IF OLD.state < 2 THEN
+ -- Decrement the old state count if record exists
+ UPDATE lease4_stat SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND state = OLD.state;
+ END IF;
+
+ IF NEW.state < 2 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ -- Insert new state record if it does not exist
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat VALUES (NEW.subnet_id, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+$stat_lease4_update$ LANGUAGE plpgsql;
+
+--
+-- Fix the v4 delete trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease4_delete() RETURNS trigger AS $stat_lease4_delete$
+BEGIN
+ IF OLD.state < 2 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease4_stat SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND OLD.state = state;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+$stat_lease4_delete$ LANGUAGE plpgsql;
+
+--
+-- Fix v6 update trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease6_update() RETURNS trigger AS $stat_lease6_update$
+BEGIN
+ IF OLD.subnet_id != NEW.subnet_id OR
+ OLD.lease_type != NEW.lease_type OR
+ OLD.state != NEW.state THEN
+ IF OLD.state < 2 THEN
+ -- Decrement the old state count if record exists
+ UPDATE lease6_stat SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+
+ IF NEW.state < 2 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ -- Insert new state record if it does not exist
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+$stat_lease6_update$ LANGUAGE plpgsql;
+
+--
+-- Fix the v6 delete trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease6_delete() RETURNS trigger AS $stat_lease6_delete$
+BEGIN
+ IF OLD.state < 2 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease6_stat SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND OLD.state = state;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+$stat_lease6_delete$ LANGUAGE plpgsql;
+
+-- Set 6.1 schema version.
+UPDATE schema_version
+ SET version = '6', minor = '1';
+
+-- Schema 6.1 specification ends here.
+
+-- Starting from this version we allow specifying multiple IP reservations
+-- for the same address in certain DHCP configurations. The server may check
+-- uniqueness of the IP addresses on its own. This is no longer checked at
+-- the database level to facilitate the use cases when a single host may
+-- get the same reserved IP address via different interfaces.
+
+-- Replace the unique index with non-unique index so the queries for
+-- hosts by IPv4 address are still efficient.
+DROP INDEX IF EXISTS key_dhcp4_ipv4_address_subnet_id;
+CREATE INDEX key_dhcp4_ipv4_address_subnet_id
+ ON hosts (ipv4_address ASC, dhcp4_subnet_id ASC);
+
+-- Replace the unique index with non-unique index so the queries for
+-- hosts by IPv6 address are still efficient.
+ALTER TABLE ipv6_reservations DROP CONSTRAINT IF EXISTS key_dhcp6_address_prefix_len;
+CREATE INDEX key_dhcp6_address_prefix_len
+ ON ipv6_reservations (address ASC, prefix_len ASC);
+
+-- Update the schema version number
+UPDATE schema_version
+ SET version = '6', minor = '2';
+
+-- Schema 6.2 specification ends here.
+
+-- This starts schema update to 7.0. It adds a lot (20+) of tables for the config backend.
+
+
+-- Adding on update trigger in MySQL is as easy as using this column definition in CREATE TABLE:
+-- modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+-- Sadly, Postgres has its own convoluted way of doing this. Luckily, the update pattern is
+-- the same in many tables, so we can define the trigger function once and the use it everywhere.
+
+-- First, we need to define a function that will do the actual job.
+-- This is used in many, many tables.
+CREATE OR REPLACE FUNCTION modification_ts_update()
+ RETURNS trigger AS $modification_ts_update$
+ BEGIN
+ new.modification_ts = CURRENT_TIMESTAMP;
+ RETURN NULL;
+ END;
+
+-- Second, we need to specify which language it was written in.
+$modification_ts_update$ LANGUAGE plpgsql;
+
+
+-- Create table modification and insert values for modification types.
+CREATE TABLE modification (
+ id smallint NOT NULL,
+ modification_type VARCHAR(32) NOT NULL,
+ PRIMARY KEY (id)
+);
+INSERT INTO modification VALUES (0,'create'), (1,'update'), (2,'delete');
+
+
+
+-- Now create the table that holds different parameter data types.
+CREATE TABLE parameter_data_type (
+ id smallint NOT NULL,
+ name VARCHAR(32) NOT NULL,
+ PRIMARY KEY (id)
+);
+INSERT INTO parameter_data_type VALUES
+ (0,'integer'),
+ (1,'real'),
+ (2,'boolean'),
+ (4,'string');
+
+
+
+-- This table doesn't exist in MySQL. However, it's nice to have an enum that explains what the values
+-- in ddns_replace_client_name field in the dhcp{4,6}_shared_network table means.
+CREATE TABLE ddns_replace_client_name_types (
+ type INT8 PRIMARY KEY NOT NULL,
+ name VARCHAR(32)
+);
+-- See enum ReplaceClientNameMode in src/lib/dhcpsrv/d2_client_cfg.h
+INSERT INTO ddns_replace_client_name_types (type, name) VALUES
+ (0, 'RCM_NEVER'),
+ (1, 'RCM_ALWAYS'),
+ (2, 'RCM_WHEN_PRESENT'),
+ (3, 'RCM_WHEN_NOT_PRESENT');
+
+
+
+-- Create table for DHCPv6 servers
+CREATE TABLE dhcp6_server (
+ id SERIAL PRIMARY KEY NOT NULL,
+ tag VARCHAR(64) NOT NULL,
+ description TEXT DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE(tag)
+);
+CREATE INDEX dhcp6_server_idx1 ON dhcp6_server (modification_ts);
+CREATE UNIQUE INDEX dhcp6_server_idx2 ON dhcp6_server(tag);
+CREATE TRIGGER dhcp6_server_modification_ts_update
+ AFTER UPDATE ON dhcp6_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+INSERT INTO dhcp6_server (tag, description) VALUES ('all','special type: all servers');
+
+-- Create a table for storing IPv6 shared networks
+CREATE TABLE dhcp6_shared_network (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) UNIQUE NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ preferred_lifetime BIGINT DEFAULT NULL,
+ rapid_commit BOOLEAN DEFAULT NULL,
+ rebind_timer BIGINT DEFAULT NULL,
+ relay TEXT DEFAULT NULL,
+ renew_timer BIGINT DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ valid_lifetime BIGINT DEFAULT NULL,
+ calculate_tee_times BOOLEAN DEFAULT NULL,
+ t1_percent float DEFAULT NULL,
+ t2_percent float DEFAULT NULL,
+ interface_id BYTEA DEFAULT NULL, -- 128 bytes
+ min_preferred_lifetime BIGINT DEFAULT NULL,
+ max_preferred_lifetime BIGINT DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ ddns_send_updates BOOLEAN DEFAULT NULL,
+ ddns_override_no_update BOOLEAN DEFAULT NULL,
+ ddns_override_client_update BOOLEAN DEFAULT NULL,
+ ddns_replace_client_name INT8 DEFAULT NULL,
+ ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL,
+ reservations_global BOOLEAN DEFAULT NULL,
+ reservations_in_subnet BOOLEAN DEFAULT NULL,
+ reservations_out_of_pool BOOLEAN DEFAULT NULL,
+ cache_threshold float DEFAULT NULL,
+ cache_max_age BIGINT DEFAULT NULL,
+ CONSTRAINT fk_ddns_replace_client_name FOREIGN KEY (ddns_replace_client_name)
+ REFERENCES ddns_replace_client_name_types (type)
+);
+CREATE INDEX dhcp6_shared_network_idx1 ON dhcp6_shared_network (name);
+
+CREATE TRIGGER dhcp6_shared_network_modification_ts_update
+ AFTER UPDATE ON dhcp6_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Now we need to create a relationship between defined shared networks and the servers
+CREATE TABLE dhcp6_shared_network_server (
+ shared_network_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (shared_network_id, server_id),
+ CONSTRAINT fk_dhcp6_shared_network_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_shared_network_server_shared_network_id FOREIGN KEY (shared_network_id)
+ REFERENCES dhcp6_shared_network (id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX dhcp6_shared_network_server_idx1 ON dhcp6_shared_network_server (modification_ts);
+CREATE INDEX dhcp6_shared_network_server_idx2 ON dhcp6_shared_network_server (server_id);
+
+-- Create a list of IPv6 subnets
+CREATE TABLE dhcp6_subnet (
+ subnet_id BIGINT PRIMARY KEY NOT NULL,
+ subnet_prefix VARCHAR(64) UNIQUE NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ preferred_lifetime BIGINT DEFAULT NULL,
+ rapid_commit BOOLEAN DEFAULT NULL,
+ rebind_timer BIGINT DEFAULT NULL,
+ relay TEXT DEFAULT NULL,
+ renew_timer BIGINT DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ shared_network_name VARCHAR(128) DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ valid_lifetime BIGINT DEFAULT NULL,
+ calculate_tee_times BOOLEAN DEFAULT NULL,
+ t1_percent float DEFAULT NULL,
+ t2_percent float DEFAULT NULL,
+ interface_id BYTEA DEFAULT NULL,
+ min_preferred_lifetime BIGINT DEFAULT NULL,
+ max_preferred_lifetime BIGINT DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ ddns_send_updates BOOLEAN DEFAULT NULL,
+ ddns_override_no_update BOOLEAN DEFAULT NULL,
+ ddns_override_client_update BOOLEAN DEFAULT NULL,
+ ddns_replace_client_name INT8 DEFAULT NULL,
+ ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL,
+ reservations_global BOOLEAN DEFAULT NULL,
+ reservations_in_subnet BOOLEAN DEFAULT NULL,
+ reservations_out_of_pool BOOLEAN DEFAULT NULL,
+ cache_threshold float DEFAULT NULL,
+ cache_max_age BIGINT DEFAULT NULL,
+ CONSTRAINT fk_dhcp6_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network (name) ON DELETE SET NULL ON UPDATE NO ACTION,
+ CONSTRAINT fk_ddns_replace_client_name FOREIGN KEY (ddns_replace_client_name)
+ REFERENCES ddns_replace_client_name_types (type)
+);
+
+CREATE TRIGGER dhcp6_subnet_modification_ts_update
+ AFTER UPDATE ON dhcp6_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+CREATE INDEX dhcp6_subnet_idx1 ON dhcp6_subnet (modification_ts);
+CREATE INDEX dhcp6_subnet_idx2 ON dhcp6_subnet (shared_network_name);
+
+
+
+-- Create a table that holds all address pools in IPv6.
+CREATE TABLE dhcp6_pool (
+ id SERIAL PRIMARY KEY NOT NULL,
+ start_address inet NOT NULL,
+ end_address inet NOT NULL,
+ subnet_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ client_class VARCHAR(128) DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ CONSTRAINT fk_dhcp6_pool_subnet_id FOREIGN KEY (subnet_id) REFERENCES dhcp6_subnet (subnet_id)
+);
+CREATE INDEX dhcp6_pool_idx1 ON dhcp6_pool (modification_ts);
+CREATE INDEX dhcp6_pool_idx2 ON dhcp6_pool (subnet_id);
+
+CREATE TRIGGER dhcp6_pool_modification_ts_update
+ AFTER UPDATE ON dhcp6_pool
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- And now the same, but for PD pools.
+CREATE TABLE dhcp6_pd_pool (
+ id SERIAL PRIMARY KEY NOT NULL,
+ prefix VARCHAR(45) NOT NULL,
+ prefix_length SMALLINT NOT NULL,
+ delegated_prefix_length SMALLINT NOT NULL,
+ subnet_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ excluded_prefix VARCHAR(45) DEFAULT NULL,
+ excluded_prefix_length SMALLINT NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ CONSTRAINT fk_dhcp6_pd_pool_subnet_id FOREIGN KEY (subnet_id) REFERENCES dhcp6_subnet(subnet_id)
+);
+
+CREATE INDEX dhcp6_pd_pool_idx1 ON dhcp6_pd_pool (modification_ts);
+CREATE INDEX dhcp6_pd_pool_idx2 ON dhcp6_pd_pool (subnet_id);
+CREATE TRIGGER dhcp6_pd_pool_modification_ts_update
+ AFTER UPDATE ON dhcp6_pd_pool
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+CREATE TABLE dhcp6_subnet_server (
+ subnet_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (subnet_id, server_id),
+ CONSTRAINT fk_dhcp6_subnet_server_server_id
+ FOREIGN KEY (server_id) REFERENCES dhcp6_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_subnet_server_subnet_id
+ FOREIGN KEY (subnet_id) REFERENCES dhcp6_subnet (subnet_id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ UNIQUE (subnet_id, server_id)
+);
+CREATE INDEX dhcp6_subnet_server_idx1 ON dhcp6_subnet_server(server_id);
+CREATE INDEX dhcp6_subnet_server_idx2 ON dhcp6_subnet_server(modification_ts);
+CREATE TRIGGER dhcp6_subnet_server_modification_ts_update
+ AFTER UPDATE ON dhcp6_subnet_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Create table for storing global DHCPv6 parameters.
+CREATE TABLE dhcp6_global_parameter (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ value TEXT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ parameter_type SMALLINT NOT NULL,
+ CONSTRAINT fk_dhcp6_global_parameter_type FOREIGN KEY (parameter_type) REFERENCES parameter_data_type(id)
+);
+
+CREATE INDEX key_dhcp6_global_parameter_idx1 ON dhcp6_global_parameter(modification_ts);
+CREATE INDEX key_dhcp6_global_parameter_idx2 ON dhcp6_global_parameter(name);
+
+CREATE TRIGGER dhcp6_global_parameter_modification_ts_update
+ AFTER UPDATE ON dhcp6_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+CREATE TABLE dhcp6_global_parameter_server (
+ parameter_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (parameter_id, server_id),
+ CONSTRAINT fk_dhcp6_global_parameter_server_parameter_id FOREIGN KEY (parameter_id)
+ REFERENCES dhcp6_global_parameter(id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_global_parameter_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server(id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+
+CREATE INDEX key_dhcp6_global_parameter_server_idx1 ON dhcp6_global_parameter_server(modification_ts);
+CREATE TRIGGER dhcp6_global_parameter_server_modification_ts_update
+ AFTER UPDATE ON dhcp6_global_parameter_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- Alter table for storing DHCPv6 options.
+ALTER TABLE dhcp6_options
+ ADD COLUMN shared_network_name VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN pool_id BIGINT DEFAULT NULL,
+ ADD COLUMN pd_pool_id BIGINT DEFAULT NULL,
+ ADD COLUMN modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ ADD CONSTRAINT fk_dhcp6_options_pd_pool FOREIGN KEY (pd_pool_id)
+ REFERENCES dhcp6_pd_pool(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ ADD CONSTRAINT fk_dhcp6_options_pool FOREIGN KEY (pool_id)
+ REFERENCES dhcp6_pool (id) ON DELETE CASCADE ON UPDATE CASCADE,
+ ADD CONSTRAINT fk_dhcp6_options_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network (name) ON DELETE CASCADE ON UPDATE CASCADE;
+
+CREATE TRIGGER dhcp6_options_modification_ts_update
+ AFTER UPDATE ON dhcp6_options
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+-- Now create a table for associating defined options with servers.
+CREATE TABLE dhcp6_options_server (
+ option_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (option_id, server_id),
+ CONSTRAINT fk_dhcp6_options_server_option_id FOREIGN KEY (option_id)
+ REFERENCES dhcp6_options (option_id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_options_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX dhcp6_options_server_idx1 ON dhcp6_options_server(server_id);
+CREATE INDEX dhcp6_options_server_idx2 ON dhcp6_options_server(modification_ts);
+CREATE TRIGGER dhcp6_options_server_modification_ts_update
+ AFTER UPDATE ON dhcp6_options_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- This table is for storing IPv6 option definitions
+CREATE TABLE dhcp6_option_def (
+ id SERIAL PRIMARY KEY UNIQUE NOT NULL,
+ code SMALLINT NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ space VARCHAR(128) NOT NULL,
+ type SMALLINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ is_array BOOLEAN NOT NULL,
+ encapsulate VARCHAR(128) NOT NULL,
+ record_types VARCHAR DEFAULT NULL,
+ user_context JSON DEFAULT NULL
+);
+CREATE INDEX dhcp6_option_def_idx1 ON dhcp6_option_def(modification_ts);
+CREATE INDEX dhcp6_option_def_idx2 ON dhcp6_option_def(code, space);
+CREATE TRIGGER dhcp6_option_def_modification_ts_update
+ AFTER UPDATE ON dhcp6_option_def
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- and another table for storing relationship between option definitions and servers.
+CREATE TABLE dhcp6_option_def_server (
+ option_def_id BIGINT NOT NULL REFERENCES dhcp6_option_def (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ server_id BIGINT NOT NULL REFERENCES dhcp6_server (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (option_def_id, server_id)
+);
+CREATE TRIGGER dhcp6_option_def_server_modification_ts_update
+ AFTER UPDATE ON dhcp6_option_def_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- Now create two tables for audit revisions...
+CREATE TABLE dhcp6_audit_revision (
+ id SERIAL PRIMARY KEY NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ log_message TEXT DEFAULT NULL,
+ server_id BIGINT DEFAULT NULL
+);
+CREATE TRIGGER dhcp6_audit_revision_modification_ts_update
+ AFTER UPDATE ON dhcp6_audit_revision
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- ... and the DHCPv6 audit itself.
+CREATE TABLE dhcp6_audit (
+ id SERIAL UNIQUE NOT NULL,
+ object_type VARCHAR(256) NOT NULL,
+ object_id BIGINT NOT NULL,
+ modification_type SMALLINT NOT NULL,
+ revision_id BIGINT NOT NULL,
+ CONSTRAINT fk_dhcp6_audit_modification_type FOREIGN KEY (modification_type)
+ REFERENCES modification (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_audit_revision FOREIGN KEY (revision_id)
+ REFERENCES dhcp6_audit_revision (id) ON DELETE NO ACTION ON UPDATE CASCADE
+);
+CREATE TRIGGER dhcp6_audit_modification_ts_update
+ AFTER UPDATE ON dhcp6_audit
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+CREATE INDEX dhcp6_audit_idx1 ON dhcp6_audit (modification_type);
+CREATE INDEX dhcp6_audit_idx2 ON dhcp6_audit (revision_id);
+
+
+-- Create table for DHCPv4 servers
+CREATE TABLE dhcp4_server (
+ id SERIAL PRIMARY KEY NOT NULL,
+ tag VARCHAR(64) NOT NULL,
+ description TEXT DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE(tag)
+);
+CREATE INDEX dhcp4_server_modification_ts ON dhcp6_server (modification_ts);
+CREATE TRIGGER dhcp4_server_modification_ts_update
+ AFTER UPDATE ON dhcp4_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+INSERT INTO dhcp4_server (tag, description) VALUES ('all','special type: all servers');
+
+-- Create table for storing global DHCPv4 parameters.
+CREATE TABLE dhcp4_global_parameter (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ value TEXT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ parameter_type SMALLINT NOT NULL,
+ CONSTRAINT fk_dhcp6_global_parameter_type FOREIGN KEY (parameter_type) REFERENCES parameter_data_type(id)
+);
+CREATE INDEX dhcp4_global_parameter_idx1 ON dhcp4_global_parameter(modification_ts);
+CREATE INDEX dhcp4_global_parameter_idx2 ON dhcp4_global_parameter(name);
+
+CREATE TRIGGER dhcp4_global_parameter_modification_ts_update
+ AFTER UPDATE ON dhcp4_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+-- and create mapping for the global DHCPv4 parameters mapping to servers
+CREATE TABLE dhcp4_global_parameter_server (
+ parameter_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (parameter_id, server_id),
+ CONSTRAINT fk_dhcp4_global_parameter_server_parameter_id FOREIGN KEY (parameter_id)
+ REFERENCES dhcp4_global_parameter(id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_global_parameter_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server(id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX key_dhcp4_global_parameter_idx1 ON dhcp4_global_parameter_server(modification_ts);
+CREATE TRIGGER dhcp4_global_parameter_server_modification_ts_update
+ AFTER UPDATE ON dhcp4_global_parameter_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- Create a table for storing IPv4 shared networks
+CREATE TABLE dhcp4_shared_network (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) UNIQUE NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ match_client_id BOOLEAN DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ rebind_timer BIGINT DEFAULT NULL,
+ relay TEXT DEFAULT NULL,
+ renew_timer BIGINT DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ valid_lifetime BIGINT DEFAULT NULL,
+ authoritative BOOLEAN DEFAULT NULL,
+ calculate_tee_times BOOLEAN DEFAULT NULL,
+ t1_percent float DEFAULT NULL,
+ t2_percent float DEFAULT NULL,
+ boot_file_name VARCHAR(128) DEFAULT NULL,
+ next_server inet DEFAULT NULL, -- let's use type inet
+ server_hostname VARCHAR(64) DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ ddns_send_updates BOOLEAN DEFAULT NULL,
+ ddns_override_no_update BOOLEAN DEFAULT NULL,
+ ddns_override_client_update BOOLEAN DEFAULT NULL,
+ ddns_replace_client_name INT8 DEFAULT NULL,
+ ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL,
+ reservations_global BOOLEAN DEFAULT NULL,
+ reservations_in_subnet BOOLEAN DEFAULT NULL,
+ reservations_out_of_pool BOOLEAN DEFAULT NULL,
+ cache_threshold float DEFAULT NULL,
+ cache_max_age BIGINT DEFAULT NULL,
+ CONSTRAINT fk_ddns_replace_client_name FOREIGN KEY (ddns_replace_client_name)
+ REFERENCES ddns_replace_client_name_types (type)
+);
+
+CREATE UNIQUE INDEX dhcp4_shared_network_idx1 ON dhcp4_shared_network (name);
+CREATE INDEX dhcp4_shared_network_idx2 ON dhcp4_shared_network (modification_ts);
+
+CREATE TRIGGER dhcp4_shared_network_modification_ts_update
+ AFTER UPDATE ON dhcp4_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Now we need to create a relationship between defined shared networks and the servers
+CREATE TABLE dhcp4_shared_network_server (
+ shared_network_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (shared_network_id, server_id),
+ CONSTRAINT fk_dhcp4_shared_network_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_shared_network_server_shared_network_id FOREIGN KEY (shared_network_id)
+ REFERENCES dhcp4_shared_network (id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX dhcp4_shared_network_server_idx1 ON dhcp4_shared_network_server (modification_ts);
+CREATE INDEX dhcp4_shared_network_server_idx2 ON dhcp4_shared_network_server (server_id);
+
+
+
+-- Create a list of IPv4 subnets
+CREATE TABLE dhcp4_subnet (
+ subnet_id BIGINT PRIMARY KEY NOT NULL,
+ subnet_prefix VARCHAR(64) UNIQUE NOT NULL,
+ interface_4o6 VARCHAR(128) DEFAULT NULL,
+ interface_id_4o6 VARCHAR(128) DEFAULT NULL,
+ subnet_4o6 VARCHAR(64) DEFAULT NULL,
+ boot_file_name VARCHAR(128) DEFAULT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ match_client_id BOOLEAN DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ next_server inet DEFAULT NULL,
+ rebind_timer BIGINT DEFAULT NULL,
+ relay TEXT DEFAULT NULL,
+ renew_timer BIGINT DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ server_hostname VARCHAR(64) DEFAULT NULL,
+ shared_network_name VARCHAR(128) DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ valid_lifetime BIGINT DEFAULT NULL,
+ authoritative BOOLEAN DEFAULT NULL,
+ calculate_tee_times BOOLEAN DEFAULT NULL,
+ t1_percent float DEFAULT NULL,
+ t2_percent float DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ ddns_send_updates BOOLEAN DEFAULT NULL,
+ ddns_override_no_update BOOLEAN DEFAULT NULL,
+ ddns_override_client_update BOOLEAN DEFAULT NULL,
+ ddns_replace_client_name INT8 DEFAULT NULL,
+ ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL,
+ reservations_global BOOLEAN DEFAULT NULL,
+ reservations_in_subnet BOOLEAN DEFAULT NULL,
+ reservations_out_of_pool BOOLEAN DEFAULT NULL,
+ cache_threshold float DEFAULT NULL,
+ cache_max_age BIGINT DEFAULT NULL,
+ CONSTRAINT fk_dhcp4_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network (name) ON DELETE SET NULL ON UPDATE NO ACTION,
+ CONSTRAINT fk_ddns_replace_client_name FOREIGN KEY (ddns_replace_client_name)
+ REFERENCES ddns_replace_client_name_types (type)
+);
+
+CREATE TRIGGER dhcp4_subnet_modification_ts_update
+ AFTER UPDATE ON dhcp4_subnet
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+CREATE INDEX dhcp4_subnet_idx1 ON dhcp4_subnet (modification_ts);
+CREATE INDEX dhcp4_subnet_idx2 ON dhcp4_subnet (shared_network_name);
+
+
+
+CREATE TABLE dhcp4_subnet_server (
+ subnet_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (subnet_id, server_id),
+ CONSTRAINT fk_dhcp6_subnet_server_server_id
+ FOREIGN KEY (server_id) REFERENCES dhcp6_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_subnet_server_subnet_id
+ FOREIGN KEY (subnet_id) REFERENCES dhcp6_subnet (subnet_id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ UNIQUE (subnet_id, server_id)
+);
+CREATE INDEX dhcp4_subnet_server_idx1 ON dhcp4_subnet_server(server_id);
+CREATE INDEX dhcp4_subnet_server_idx2 ON dhcp4_subnet_server(modification_ts);
+CREATE TRIGGER dhcp4_subnet_server_modification_ts_update
+ AFTER UPDATE ON dhcp4_subnet_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Create a table that holds all address pools in IPv4.
+CREATE TABLE dhcp4_pool (
+ id SERIAL PRIMARY KEY NOT NULL,
+ start_address inet NOT NULL,
+ end_address inet NOT NULL,
+ subnet_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ client_class VARCHAR(128) DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ CONSTRAINT fk_dhcp4_pool_subnet_id FOREIGN KEY (subnet_id) REFERENCES dhcp4_subnet (subnet_id)
+);
+CREATE INDEX dhcp4_pool_idx1 ON dhcp4_pool (modification_ts);
+CREATE INDEX dhcp4_pool_idx2 ON dhcp4_pool (subnet_id);
+
+CREATE TRIGGER dhcp4_pool_modification_ts_update
+ AFTER UPDATE ON dhcp4_pool
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- ALTER table for storing DHCPv4 options.
+ALTER TABLE dhcp4_options
+ ADD COLUMN shared_network_name VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN pool_id BIGINT DEFAULT NULL,
+ ADD COLUMN modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ ADD CONSTRAINT fk_dhcp4_options_pool FOREIGN KEY (pool_id)
+ REFERENCES dhcp4_pool (id) ON DELETE CASCADE ON UPDATE CASCADE,
+ ADD CONSTRAINT fk_dhcp4_options_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network (name) ON DELETE CASCADE ON UPDATE CASCADE;
+
+CREATE TRIGGER dhcp4_options_modification_ts_update
+ AFTER UPDATE ON dhcp4_options
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Now create a table for associating defined v4 options with servers.
+CREATE TABLE dhcp4_options_server (
+ option_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (option_id, server_id),
+ CONSTRAINT fk_dhcp4_options_server_option_id FOREIGN KEY (option_id)
+ REFERENCES dhcp4_options (option_id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_options_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX dhcp4_options_server_idx1 ON dhcp4_options_server(server_id);
+CREATE INDEX dhcp4_options_server_idx2 ON dhcp4_options_server(modification_ts);
+CREATE TRIGGER dhcp4_options_server_modification_ts_update
+ AFTER UPDATE ON dhcp4_options_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- This table is for storing IPv4 option definitions
+CREATE TABLE dhcp4_option_def (
+ id SERIAL PRIMARY KEY UNIQUE NOT NULL,
+ code SMALLINT NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ space VARCHAR(128) NOT NULL,
+ type SMALLINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ is_array BOOLEAN NOT NULL,
+ encapsulate VARCHAR(128) NOT NULL,
+ record_types VARCHAR DEFAULT NULL,
+ user_context JSON DEFAULT NULL
+);
+CREATE INDEX dhcp4_option_def_idx1 ON dhcp4_option_def(modification_ts);
+CREATE INDEX dhcp4_option_def_idx2 ON dhcp4_option_def(code, space);
+CREATE TRIGGER dhcp4_option_def_modification_ts_update
+ AFTER UPDATE ON dhcp4_option_def
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- and another table for storing relationship between option definitions and servers.
+CREATE TABLE dhcp4_option_def_server (
+ option_def_id BIGINT NOT NULL REFERENCES dhcp6_option_def (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ server_id BIGINT NOT NULL REFERENCES dhcp4_server (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (option_def_id, server_id)
+);
+CREATE TRIGGER dhcp4_option_def_server_modification_ts_update
+ AFTER UPDATE ON dhcp4_option_def_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Now create two tables for audit revisions...
+CREATE TABLE dhcp4_audit_revision (
+ id SERIAL PRIMARY KEY NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ log_message TEXT DEFAULT NULL,
+ server_id BIGINT DEFAULT NULL
+);
+CREATE TRIGGER dhcp4_audit_revision_modification_ts_update
+ AFTER UPDATE ON dhcp4_audit_revision
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- ... and the DHCPv4 audit itself.
+CREATE TABLE dhcp4_audit (
+ id SERIAL UNIQUE NOT NULL,
+ object_type VARCHAR(256) NOT NULL,
+ object_id BIGINT NOT NULL,
+ modification_type SMALLINT NOT NULL,
+ revision_id BIGINT NOT NULL,
+ CONSTRAINT fk_dhcp4_audit_modification_type FOREIGN KEY (modification_type)
+ REFERENCES modification (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_audit_revision FOREIGN KEY (revision_id)
+ REFERENCES dhcp4_audit_revision (id) ON DELETE NO ACTION ON UPDATE CASCADE
+);
+CREATE TRIGGER dhcp4_audit_modification_ts_update
+ AFTER UPDATE ON dhcp4_audit
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+CREATE INDEX dhcp4_audit_idx1 ON dhcp4_audit (modification_type);
+CREATE INDEX dhcp4_audit_idx2 ON dhcp4_audit (revision_id);
+
+
+-- Stores a TEXT value to a session variable
+-- name name of session variable to set
+-- value TEXT value to store
+CREATE OR REPLACE FUNCTION set_session_value(name text, value TEXT)
+RETURNS VOID
+AS $$
+DECLARE
+BEGIN
+ PERFORM set_config(name, value, false);
+ RETURN;
+
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'set_session_value(%) : value:[%] failed, sqlstate: %', name, value, sqlstate;
+END;$$
+LANGUAGE plpgsql;
+
+-- Stores a BIGINT value to a session variable
+-- Note the value converted to TEXT and then stored as Postgresql does
+-- not support any other data type in session variables.
+-- name name of session variable to set
+-- value BIGINT value to store
+CREATE OR REPLACE FUNCTION set_session_value(name text, value BIGINT)
+RETURNS VOID
+AS $$
+BEGIN
+ PERFORM set_config(name, cast(value as text), false);
+ RETURN;
+
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'set_session_value(%) : value:[%] failed, sqlstate: %', name, value, sqlstate;
+END;$$
+LANGUAGE plpgsql;
+
+-- Stores a BOOLEAN value to a session variable
+-- Note the value converted to TEXT and then stored as Postgresql does
+-- not support any other data type in session variables.
+-- name name of session variable to set
+-- value BOOLEAN value to store
+CREATE OR REPLACE FUNCTION set_session_value(name text, value BOOLEAN)
+RETURNS VOID
+AS $$
+BEGIN
+ PERFORM set_config(name, cast(value as text), false);
+ RETURN;
+
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'set_session_value(%) : value:[%] failed, sqlstate: %', name, value, sqlstate;
+END;$$
+LANGUAGE plpgsql;
+
+
+-- Fetches a text value from the session configuration.
+-- param name name of the session variable to fetch
+-- If the name is not found it returns NULL.
+-- Postgresql allows you to store custom session values
+-- but throws an exception if they have not first been
+-- set. This allows us to be a bit more graceful.
+CREATE OR REPLACE FUNCTION get_session_value(name TEXT)
+RETURNS TEXT
+AS $$
+DECLARE
+ text_value TEXT := '';
+BEGIN
+ text_value = current_setting(name);
+ RETURN(text_value);
+
+ EXCEPTION
+ WHEN undefined_object THEN
+ -- Variable has not been initialized so return NULL
+ RETURN NULL;
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'get_session_value(%, TEXT) failed, sqlstate: %', name, sqlstate;
+END;$$
+LANGUAGE plpgsql;
+
+-- Fetches an BIGINT value from the session configuration.
+-- param name name of the session variable to fetch
+-- If the name is not found it returns zero.
+CREATE OR REPLACE FUNCTION get_session_big_int(name text)
+RETURNS BIGINT
+AS $$
+DECLARE
+ int_value BIGINT := 0;
+ text_value TEXT := '';
+BEGIN
+ text_value = get_session_value(name);
+ IF text_value is NULL or text_value = '' THEN
+ RETURN(0);
+ END IF;
+
+ int_value = cast(text_value as BIGINT);
+ RETURN(int_value);
+
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'get_session_big_int(%) failed - text:[%] , sqlstate: %', name, text_value, sqlstate;
+
+END;$$
+LANGUAGE plpgsql;
+
+-- Fetches an BOOLEAN value from the session configuration.
+-- param name name of the session variable to fetch
+-- If the name is not found it returns zero.
+CREATE OR REPLACE FUNCTION get_session_boolean(name text)
+RETURNS BOOLEAN
+AS $$
+DECLARE
+ bool_value BOOLEAN := false;
+ text_value TEXT := '';
+BEGIN
+ text_value = get_session_value(name);
+ IF text_value is NULL or text_value = '' THEN
+ RETURN(false);
+ END IF;
+
+ bool_value = cast(text_value as BOOLEAN);
+ RETURN(bool_value);
+
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'get_session_boolean(%) failed - text:[%] , sqlstate: %', name, text_value, sqlstate;
+
+END;$$
+LANGUAGE plpgsql;
+
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp4_audit_revision table and sets appropriate session
+-- variables to be used while creating the audit entries
+-- by triggers. This procedure should be called at the
+-- beginning of a transaction which modifies configuration
+-- data in the database, e.g. when new subnet is added.
+--
+-- Parameters:
+-- - audit_ts timestamp to be associated with the audit
+-- revision.
+-- - server_tag is used to retrieve the server_id which
+-- associates the changes applied with the particular
+-- server or all servers.
+-- - audit_log_message is a log message associates with
+-- the audit revision.
+-- - cascade_transaction is assigned to a session
+-- variable which is used in some triggers to determine
+-- if the audit entry should be created for them or
+-- not. Specifically, this is used when DHCP options
+-- are inserted, updated or deleted. If such modification
+-- is a part of the larger change (e.g. change in the
+-- subnet the options belong to) the dedicated audit
+-- entry for options must not be created. On the other
+-- hand, if the global option is being added, the
+-- audit entry for the option must be created because
+-- it is the sole object modified in that case.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createAuditRevisionDHCP4(audit_ts TIMESTAMP WITH TIME ZONE,
+ server_tag VARCHAR(64),
+ audit_log_message TEXT,
+ cascade_transaction BOOLEAN)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ disable_audit BOOLEAN := false;
+ audit_revision_id BIGINT;
+ srv_id BIGINT;
+BEGIN
+ -- Fetch session value for disable_audit.
+ disable_audit := get_session_boolean('kea.disable_audit');
+ IF disable_audit = false THEN
+ SELECT id INTO srv_id FROM dhcp4_server WHERE tag = server_tag;
+ INSERT INTO dhcp4_audit_revision (modification_ts, server_id, log_message)
+ VALUES (audit_ts, srv_id, audit_log_message) returning id INTO audit_revision_id;
+
+ -- Update pertinent session variables.
+ PERFORM set_session_value('kea.audit_revision_id', audit_revision_id);
+ PERFORM set_session_value('kea.cascade_transaction', cascade_transaction);
+ END IF;
+ RETURN;
+END;$$;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp4_audit table. It should be called from the
+-- triggers of the tables where the config modifications
+-- are applied. The audit_revision_id variable contains
+-- the revision id to be placed in the audit entries.
+--
+-- The following parameters are passed to this procedure:
+-- - object_type_val: name of the table to be associated
+-- with the applied changes.
+-- - object_id_val: identifier of the modified object in
+-- that table.
+-- - modification_type_val: string value indicating the
+-- type of the change, i.e. 'create', 'update' or
+-- 'delete'.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- ----------------------------------------------------
+CREATE OR REPLACE FUNCTION createAuditEntryDHCP4(object_type_val VARCHAR(256),
+ object_id_val BIGINT,
+ modification_type_val VARCHAR(32))
+RETURNS VOID
+LANGUAGE plpgsql
+as $$
+DECLARE
+ audit_revision_id BIGINT;
+ disable_audit BOOLEAN := false;
+BEGIN
+ -- Fetch session value for disable_audit.
+ disable_audit := get_session_boolean('kea.disable_audit');
+
+ IF disable_audit IS NULL OR disable_audit = false THEN
+ -- Fetch session value most recently created audit_revision_id.
+ audit_revision_id := get_session_big_int('kea.audit_revision_id');
+ INSERT INTO dhcp4_audit (object_type, object_id, modification_type, revision_id)
+ VALUES (object_type_val, object_id_val,
+ (SELECT id FROM modification WHERE modification_type = modification_type_val),
+ audit_revision_id);
+ END IF;
+ RETURN;
+END;$$;
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv4 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp4_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) UNIQUE NOT NULL,
+ test TEXT,
+ next_server INET DEFAULT NULL,
+ server_hostname VARCHAR(128) DEFAULT NULL,
+ boot_file_name VARCHAR(512) DEFAULT NULL,
+ only_if_required BOOLEAN NOT NULL DEFAULT false,
+ valid_lifetime BIGINT DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ depend_on_known_directly BOOLEAN NOT NULL DEFAULT false,
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX key_dhcp4_client_class_modification_ts on dhcp4_client_class (modification_ts);
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp4_client_class table has a corresponding row
+-- in the dhcp4_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp4_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp4_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_indirectly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp4_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_order (
+ class_id BIGINT PRIMARY KEY NOT NULL,
+ order_index BIGINT NOT NULL,
+ depend_on_known_indirectly BOOLEAN NOT NULL DEFAULT false,
+ CONSTRAINT fk_dhcp4_client_class_order_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id) ON DELETE CASCADE
+);
+
+CREATE INDEX key_dhcp4_client_class_order_index on dhcp4_client_class_order (order_index);
+
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass4Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BIGINT := 0;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp4_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp4_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp4_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp4_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp4_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency AS d
+ INNER JOIN dhcp4_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- ON CONFLICT required 9.5 or later
+ UPDATE dhcp4_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+ RETURN;
+END;$$;
+
+-- -----------------------------------------------------------------------
+-- Trigger procedure to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_AINS() RETURNS trigger AS $dhcp4_client_class_AINS$
+BEGIN
+ PERFORM setClientClass4Order(NEW.id, NEW.follow_class_name, NULL);
+ PERFORM createAuditEntryDHCP4('dhcp4_client_class', NEW.id, 'create');
+ RETURN NULL;
+END;
+$dhcp4_client_class_AINS$
+LANGUAGE plpgsql;
+
+-- Create dhcp4_client_class insert trigger
+CREATE TRIGGER dhcp4_client_class_AINS
+ AFTER INSERT ON dhcp4_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_client_class_AINS();
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_AUPD() RETURNS trigger AS $dhcp4_client_class_AUPD$
+BEGIN
+ PERFORM set_session_value('kea.depend_on_known_directly', OLD.depend_on_known_directly);
+ PERFORM set_session_value('kea.client_class_id', NEW.id);
+ PERFORM setClientClass4Order(NEW.id, NEW.follow_class_name, OLD.follow_class_name);
+ PERFORM createAuditEntryDHCP4('dhcp4_client_class', NEW.id, 'update');
+ RETURN NULL;
+END;
+$dhcp4_client_class_AUPD$
+LANGUAGE plpgsql;
+
+-- Create dhcp4_client_class update insert trigger
+CREATE TRIGGER dhcp4_client_class_AUPD
+ AFTER UPDATE ON dhcp4_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_client_class_AUPD();
+
+-- -----------------------------------------------------------------------
+-- Trigger procedure to create the audit entry for client class delete.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_ADEL() RETURNS trigger AS $dhcp4_client_class_ADEL$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_client_class', OLD.id, 'delete');
+ RETURN NULL;
+END;
+$dhcp4_client_class_ADEL$
+LANGUAGE plpgsql;
+
+-- Create dhcp4_client_class delete trigger
+CREATE TRIGGER dhcp4_client_class_ADEL
+ AFTER DELETE ON dhcp4_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_client_class_ADEL();
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp4_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp4_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_dependency (
+ class_id BIGINT PRIMARY KEY NOT NULL,
+ dependency_id BIGINT NOT NULL,
+
+ CONSTRAINT dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp4_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp4_client_class (id)
+);
+
+CREATE INDEX dhcp4_client_class_dependency_id_idx on dhcp4_client_class_dependency (dependency_id);
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - p_class_id id client class,
+-- - p_dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION checkDHCPv4ClientClassDependency(p_class_id BIGINT,
+ p_dependency_id BIGINT)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ class_index BIGINT;
+ dependency_index BIGINT;
+BEGIN
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF p_class_id IS NULL THEN
+ RAISE EXCEPTION 'Client class id must not be NULL.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ IF p_dependency_id IS NULL THEN
+ RAISE EXCEPTION 'Class dependency id must not be NULL.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Dependencies on self make no sense.
+ IF p_class_id = p_dependency_id THEN
+ RAISE EXCEPTION 'Client class must not have dependency on self.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check position of our class in the hierarchy.
+ SELECT o.order_index INTO class_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = p_class_id;
+
+ IF class_index IS NULL THEN
+ RAISE EXCEPTION 'Client class with id % does not exist.', p_class_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check position of the dependency.
+ SELECT o.order_index INTO dependency_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = p_dependency_id;
+
+ IF dependency_index IS NULL THEN
+ RAISE EXCEPTION 'Dependency class with id % does not exist.', p_dependency_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ RAISE EXCEPTION
+ 'Client class with id % must not depend on class defined later with id %',
+ p_class_id, p_dependency_id USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp4_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_server AS t1
+ LEFT JOIN dhcp4_client_class_server AS t2
+ ON t2.class_id = p_dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = p_class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unmet dependencies for client class with id %', class_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+ RETURN;
+END;$$;
+
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_check_dependency_BINS()
+ RETURNS trigger AS $dhcp4_client_class_check_dependency_BINS$
+BEGIN
+ PERFORM checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NULL;
+END;
+$dhcp4_client_class_check_dependency_BINS$
+LANGUAGE plpgsql;
+
+-- Create dhcp4_client_class_check_dependency_BINS before insert trigger.
+CREATE TRIGGER dhcp4_client_class_check_dependency_BINS
+ BEFORE INSERT ON dhcp4_client_class_dependency
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_client_class_check_dependency_BINS();
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION updateDHCPv4ClientClassKnownDependency(client_class_id BIGINT,
+ dependency_id BIGINT)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ dependency BOOLEAN;
+BEGIN
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SELECT depend_on_known_directly INTO dependency FROM dhcp4_client_class
+ WHERE id = dependency_id;
+
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = false THEN
+ SELECT depend_on_known_indirectly INTO dependency FROM dhcp4_client_class_order
+ WHERE class_id = dependency_id;
+ END IF;
+
+ IF dependency = true THEN
+ UPDATE dhcp4_client_class_order
+ SET depend_on_known_indirectly = true
+ WHERE class_id = client_class_id;
+ END IF;
+ RETURN;
+END;$$;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_dependency_AINS()
+ RETURNS TRIGGER AS $dhcp4_client_class_dependency_AINS$
+BEGIN
+ PERFORM updateDHCPv4ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NULL;
+END;
+$dhcp4_client_class_dependency_AINS$
+LANGUAGE plpgsql;
+
+-- Create dhcp4_client_class_check_dependency_AINS after insert trigger.
+CREATE TRIGGER dhcp4_client_class_dependency_AINS
+ AFTER INSERT ON dhcp4_client_class_dependency
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_client_class_dependency_AINS();
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv4 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION checkDHCPv4ClientClassKnownDependencyChange()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ depended BOOLEAN := false;
+ depends BOOLEAN := false;
+ client_class_id BIGINT;
+ depend_on_known_directly BOOLEAN;
+ depend_on_known_indirectly BOOLEAN;
+BEGIN
+
+ -- Session variables are set upon a client class update.
+ client_class_id := get_session_big_int('kea.client_class_id');
+ IF client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency
+ WHERE dependency_id = client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ depend_on_known_directly := get_session_boolean('kea.depend_on_known_directly');
+ depend_on_known_indirectly := get_session_boolean('kea.depend_on_known_indirectly');
+ IF depend_on_known_directly = true OR depend_on_known_indirectly = true THEN
+ SET depended = true;
+ END IF;
+
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SELECT depend_on_known_directly INTO depends FROM dhcp4_client_class
+ WHERE id = client_class_id;
+
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = false THEN
+ SELECT depend_on_known_indirectly INTO depends FROM dhcp4_client_class_order
+ WHERE class_id = client_class_id;
+ END IF;
+
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ RAISE EXCEPTION 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+ END IF;
+ END IF;
+ RETURN;
+END;$$;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv4 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_server (
+ class_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ CONSTRAINT fk_dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp4_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+);
+
+CREATE INDEX fk_dhcp4_client_class_server_id ON dhcp4_client_class_server (server_id);
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp6_audit_revision table and sets appropriate session
+-- variables to be used while creating the audit entries
+-- by triggers. This procedure should be called at the
+-- beginning of a transaction which modifies configuration
+-- data in the database, e.g. when new subnet is added.
+--
+-- Parameters:
+-- - audit_ts timestamp to be associated with the audit
+-- revision.
+-- - server_tag is used to retrieve the server_id which
+-- associates the changes applied with the particular
+-- server or all servers.
+-- - audit_log_message is a log message associates with
+-- the audit revision.
+-- - cascade_transaction is assigned to a session
+-- variable which is used in some triggers to determine
+-- if the audit entry should be created for them or
+-- not. Specifically, this is used when DHCP options
+-- are inserted, updated or deleted. If such modification
+-- is a part of the larger change (e.g. change in the
+-- subnet the options belong to) the dedicated audit
+-- entry for options must not be created. On the other
+-- hand, if the global option is being added, the
+-- audit entry for the option must be created because
+-- it is the sole object modified in that case.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createAuditRevisionDHCP6(audit_ts TIMESTAMP WITH TIME ZONE,
+ server_tag VARCHAR(64),
+ audit_log_message TEXT,
+ cascade_transaction BOOLEAN)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ disable_audit BOOLEAN := false;
+ audit_revision_id BIGINT;
+ srv_id BIGINT;
+BEGIN
+ -- Fetch session value for disable_audit.
+ disable_audit := get_session_boolean('kea.disable_audit');
+ IF disable_audit = false THEN
+ SELECT id INTO srv_id FROM dhcp6_server WHERE tag = server_tag;
+ INSERT INTO dhcp6_audit_revision (modification_ts, server_id, log_message)
+ VALUES (audit_ts, srv_id, audit_log_message) returning id INTO audit_revision_id;
+
+ -- Update pertinent session variables.
+ PERFORM set_session_value('kea.audit_revision_id', audit_revision_id);
+ PERFORM set_session_value('kea.cascade_transaction', cascade_transaction);
+ END IF;
+ RETURN;
+END;$$;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp6_audit table. It should be called from the
+-- triggers of the tables where the config modifications
+-- are applied. The audit_revision_id variable contains
+-- the revision id to be placed in the audit entries.
+--
+-- The following parameters are passed to this procedure:
+-- - object_type_val: name of the table to be associated
+-- with the applied changes.
+-- - object_id_val: identifier of the modified object in
+-- that table.
+-- - modification_type_val: string value indicating the
+-- type of the change, i.e. 'create', 'update' or
+-- 'delete'.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- ----------------------------------------------------
+CREATE OR REPLACE FUNCTION createAuditEntryDHCP6(object_type_val VARCHAR(256),
+ object_id_val BIGINT,
+ modification_type_val VARCHAR(32))
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ audit_revision_id BIGINT;
+ disable_audit BOOLEAN := false;
+BEGIN
+ -- Fetch session value for disable_audit.
+ disable_audit := get_session_boolean('kea.disable_audit');
+ IF disable_audit = false THEN
+ -- Fetch session value most recently created audit_revision_id.
+ audit_revision_id := get_session_big_int('kea.audit_revision_id');
+ INSERT INTO dhcp6_audit (object_type, object_id, modification_type, revision_id)
+ VALUES (object_type_val, object_id_val,
+ (SELECT id FROM modification WHERE modification_type = modification_type_val),
+ audit_revision_id);
+ END IF;
+ RETURN;
+END;$$;
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv6 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp6_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) UNIQUE NOT NULL,
+ test TEXT,
+ only_if_required BOOLEAN NOT NULL DEFAULT false,
+ valid_lifetime BIGINT DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ depend_on_known_directly BOOLEAN NOT NULL DEFAULT false,
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX key_dhcp6_client_class_modification_ts on dhcp6_client_class (modification_ts);
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp6_client_class table has a corresponding row
+-- in the dhcp6_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp6_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp6_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_indirectly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp6_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_order (
+ class_id BIGINT PRIMARY KEY NOT NULL,
+ order_index BIGINT NOT NULL,
+ depend_on_known_indirectly BOOLEAN NOT NULL DEFAULT false,
+ CONSTRAINT fk_dhcp6_client_class_order_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE
+);
+
+CREATE INDEX key_dhcp6_client_class_order_index on dhcp6_client_class_order (order_index);
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- new_follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - new_follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass6Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BIGINT := 0;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp6_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp6_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp6_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp6_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp6_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency AS d
+ INNER JOIN dhcp6_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM - note that ON CONFLICT requires PostgreSQL 9.5 or later.
+ UPDATE dhcp6_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+ RETURN;
+END;$$;
+
+-- -----------------------------------------------------------------------
+-- Trigger procedure to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_AINS() RETURNS trigger AS $dhcp6_client_class_AINS$
+BEGIN
+ PERFORM setClientClass6Order(NEW.id, NEW.follow_class_name, NULL);
+ PERFORM createAuditEntryDHCP6('dhcp6_client_class', NEW.id, 'create');
+ RETURN NULL;
+END;
+$dhcp6_client_class_AINS$
+LANGUAGE plpgsql;
+
+-- Create dhcp6_client_class insert trigger
+CREATE TRIGGER dhcp6_client_class_AINS
+ AFTER INSERT ON dhcp6_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_client_class_AINS();
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_AUPD() RETURNS trigger AS $dhcp6_client_class_AUPD$
+BEGIN
+ PERFORM set_session_value('kea.depend_on_known_directly', OLD.depend_on_known_directly);
+ PERFORM set_session_value('kea.client_class_id', NEW.id);
+ PERFORM setClientClass6Order(NEW.id, NEW.follow_class_name, OLD.follow_class_name);
+ PERFORM createAuditEntryDHCP6('dhcp6_client_class', NEW.id, 'update');
+ RETURN NULL;
+END;
+$dhcp6_client_class_AUPD$
+LANGUAGE plpgsql;
+
+-- Create dhcp6_client_class update insert trigger
+CREATE TRIGGER dhcp6_client_class_AUPD
+ AFTER UPDATE ON dhcp6_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_client_class_AUPD();
+
+-- -----------------------------------------------------------------------
+-- Trigger procedure to create the audit entry for client class delete.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_ADEL() RETURNS trigger AS $dhcp6_client_class_ADEL$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_client_class', OLD.id, 'delete');
+ RETURN NULL;
+END;
+$dhcp6_client_class_ADEL$
+LANGUAGE plpgsql;
+
+-- Create dhcp6_client_class delete trigger
+CREATE TRIGGER dhcp6_client_class_ADEL
+ AFTER DELETE ON dhcp6_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_client_class_ADEL();
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp6_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp6_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_dependency (
+ class_id BIGINT PRIMARY KEY NOT NULL,
+ dependency_id BIGINT NOT NULL,
+
+ CONSTRAINT dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp6_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp6_client_class (id)
+);
+
+CREATE INDEX dhcp6_client_class_dependency_id_idx on dhcp6_client_class_dependency (dependency_id);
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - p_class_id id client class,
+-- - p_dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION checkDHCPv6ClientClassDependency(p_class_id BIGINT,
+ p_dependency_id BIGINT)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ class_index BIGINT;
+ dependency_index BIGINT;
+BEGIN
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF p_class_id IS NULL THEN
+ RAISE EXCEPTION 'Client class id must not be NULL.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ IF p_dependency_id IS NULL THEN
+ RAISE EXCEPTION 'Class dependency id must not be NULL.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Dependencies on self make no sense.
+ IF p_class_id = p_dependency_id THEN
+ RAISE EXCEPTION 'Client class must not have dependency on self.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check position of our class in the hierarchy.
+ SELECT o.order_index INTO class_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = p_class_id;
+
+ IF class_index IS NULL THEN
+ RAISE EXCEPTION 'Client class with id % does not exist.', p_class_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check position of the dependency.
+ SELECT o.order_index INTO dependency_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = p_dependency_id;
+
+ IF dependency_index IS NULL THEN
+ RAISE EXCEPTION 'Dependency class with id % does not exist.', p_dependency_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ RAISE EXCEPTION
+ 'Client class with id % must not depend on class defined later with id %',
+ p_class_id, p_dependency_id USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp6_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_server AS t1
+ LEFT JOIN dhcp6_client_class_server AS t2
+ ON t2.class_id = p_dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = p_class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unmet dependencies for client class with id %', p_class_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+ RETURN;
+END;$$;
+
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_check_dependency_BINS()
+ RETURNS trigger AS $dhcp6_client_class_check_dependency_BINS$
+BEGIN
+ PERFORM checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NULL;
+END;
+$dhcp6_client_class_check_dependency_BINS$
+LANGUAGE plpgsql;
+
+-- Create dhcp6_client_class_check_dependency_BINS before insert trigger.
+CREATE TRIGGER dhcp6_client_class_check_dependency_BINS
+ BEFORE INSERT ON dhcp6_client_class_dependency
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_client_class_check_dependency_BINS();
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION updateDHCPv6ClientClassKnownDependency(client_class_id BIGINT,
+ dependency_id BIGINT)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ dependency BOOLEAN;
+BEGIN
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SELECT depend_on_known_directly INTO dependency FROM dhcp6_client_class
+ WHERE id = dependency_id;
+
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = false THEN
+ SELECT depend_on_known_indirectly INTO dependency FROM dhcp6_client_class_order
+ WHERE class_id = dependency_id;
+ END IF;
+
+ IF dependency = true THEN
+ UPDATE dhcp6_client_class_order
+ SET depend_on_known_indirectly = true
+ WHERE class_id = client_class_id;
+ END IF;
+ RETURN;
+END;$$;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_dependency_AINS()
+ RETURNS TRIGGER AS $dhcp6_client_class_dependency_AINS$
+BEGIN
+ PERFORM updateDHCPv6ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NULL;
+END;
+$dhcp6_client_class_dependency_AINS$
+LANGUAGE plpgsql;
+
+-- Create dhcp6_client_class_check_dependency_AINS after insert trigger.
+CREATE TRIGGER dhcp6_client_class_dependency_AINS
+ AFTER INSERT ON dhcp6_client_class_dependency
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_client_class_dependency_AINS();
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv6 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION checkDHCPv6ClientClassKnownDependencyChange()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ depended BOOLEAN := false;
+ depends BOOLEAN := false;
+ client_class_id BIGINT;
+ depend_on_known_directly BOOLEAN;
+ depend_on_known_indirectly BOOLEAN;
+BEGIN
+
+ -- Session variables are set upon a client class update.
+ client_class_id := get_session_big_int('kea.client_class_id');
+ IF client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency
+ WHERE dependency_id = client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ depend_on_known_directly := get_session_boolean('kea.depend_on_known_directly');
+ depend_on_known_indirectly := get_session_boolean('kea.depend_on_known_indirectly');
+ IF depend_on_known_directly = true OR depend_on_known_indirectly = true THEN
+ SET depended = true;
+ END IF;
+
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SELECT depend_on_known_directly INTO depends FROM dhcp6_client_class
+ WHERE id = client_class_id;
+
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = false THEN
+ SELECT depend_on_known_indirectly INTO depends FROM dhcp6_client_class_order
+ WHERE class_id = client_class_id;
+ END IF;
+
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ RAISE EXCEPTION 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+ END IF;
+ END IF;
+ RETURN;
+END;$$;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv6 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_server (
+ class_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ CONSTRAINT fk_dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp6_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+);
+
+CREATE INDEX fk_dhcp6_client_class_server_id ON dhcp6_client_class_server (server_id);
+
+-- Trigger function for dhcp4_pool_BDEL called BEFORE DELETE on dhcp4_pool
+-- It removes pool specific options upon removal of the pool.
+CREATE OR REPLACE FUNCTION func_dhcp4_pool_BDEL() RETURNS TRIGGER AS $dhcp4_pool_BDEL$
+BEGIN
+ DELETE FROM dhcp4_options WHERE scope_id = 5 AND pool_id = OLD.id;
+ RETURN OLD;
+END;
+$dhcp4_pool_BDEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_pool_BDEL
+ BEFORE DELETE ON dhcp4_pool
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_pool_BDEL();
+
+-- Trigger function for dhcp6_pool_BDEL called BEFORE DELETE on dhcp6_pool
+-- It removes pool specific options upon removal of the pool.
+CREATE OR REPLACE FUNCTION func_dhcp6_pool_BDEL() RETURNS TRIGGER AS $dhcp6_pool_BDEL$
+BEGIN
+ DELETE FROM dhcp6_options WHERE scope_id = 5 AND pool_id = OLD.id;
+ RETURN OLD;
+END;
+$dhcp6_pool_BDEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_pool_BDEL
+ BEFORE DELETE ON dhcp6_pool
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_pool_BDEL();
+
+-- Trigger function for dhcp4_global_parameter_AINS called AFTER INSERT on dhcp4_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp4_global_parameter_AINS() RETURNS TRIGGER AS $dhcp4_global_parameter_AINS$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_global_parameter', NEW.id, 'create');
+ RETURN NULL;
+END;
+$dhcp4_global_parameter_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_global_parameter_AINS
+ AFTER INSERT ON dhcp4_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_global_parameter_AINS();
+
+
+-- Trigger function for dhcp4_global_parameter_AUPD called AFTER UPDATE on dhcp4_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp4_global_parameter_AUPD() RETURNS TRIGGER AS $dhcp4_global_parameter_AUPD$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_global_parameter', NEW.id, 'update');
+ RETURN NULL;
+END;
+$dhcp4_global_parameter_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_global_parameter_AUPD
+ AFTER UPDATE ON dhcp4_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_global_parameter_AUPD();
+
+
+-- Trigger function for dhcp4_global_parameter_ADEL called AFTER DELETE on dhcp4_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp4_global_parameter_ADEL() RETURNS TRIGGER AS $dhcp4_global_parameter_ADEL$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_global_parameter', OLD.id, 'delete');
+ RETURN NULL;
+END;
+$dhcp4_global_parameter_ADEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_global_parameter_ADEL
+ AFTER DELETE ON dhcp4_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_global_parameter_ADEL();
+
+-- Trigger function for dhcp4_subnet_AINS called AFTER INSERT on dhcp4_subnet
+CREATE OR REPLACE FUNCTION func_dhcp4_subnet_AINS() RETURNS TRIGGER AS $dhcp4_subnet_AINS$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_subnet', NEW.subnet_id, 'create');
+ RETURN NULL;
+END;
+$dhcp4_subnet_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_subnet_AINS
+ AFTER INSERT ON dhcp4_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_subnet_AINS();
+
+
+-- Trigger function for dhcp4_subnet_AUPD called AFTER UPDATE on dhcp4_subnet
+CREATE OR REPLACE FUNCTION func_dhcp4_subnet_AUPD() RETURNS TRIGGER AS $dhcp4_subnet_AUPD$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_subnet', NEW.subnet_id, 'update');
+ RETURN NULL;
+END;
+$dhcp4_subnet_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_subnet_AUPD
+ AFTER UPDATE ON dhcp4_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_subnet_AUPD();
+
+
+-- Trigger function for dhcp4_shared_network_AINS called AFTER INSERT on dhcp4_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp4_shared_network_AINS() RETURNS TRIGGER AS $dhcp4_shared_network_AINS$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_shared_network', NEW.id, 'create');
+ RETURN NULL;
+END;
+$dhcp4_shared_network_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_shared_network_AINS
+ AFTER INSERT ON dhcp4_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_shared_network_AINS();
+
+-- Trigger function for dhcp4_shared_network_AUPD called AFTER UPDATE on dhcp4_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp4_shared_network_AUPD() RETURNS TRIGGER AS $dhcp4_shared_network_AUPD$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_shared_network', NEW.id, 'update');
+ RETURN NULL;
+END;
+$dhcp4_shared_network_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_shared_network_AUPD
+ AFTER UPDATE ON dhcp4_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_shared_network_AUPD();
+
+-- Trigger function for dhcp4_shared_network_ADEL called AFTER DELETE on dhcp4_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp4_shared_network_ADEL() RETURNS TRIGGER AS $dhcp4_shared_network_ADEL$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, 'delete');
+ RETURN NULL;
+END;
+$dhcp4_shared_network_ADEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_shared_network_ADEL
+ AFTER DELETE ON dhcp4_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_shared_network_ADEL();
+
+-- Trigger function for dhcp4_option_def_AINS called AFTER INSERT on dhcp4_option_def
+CREATE OR REPLACE FUNCTION func_dhcp4_option_def_AINS() RETURNS TRIGGER AS $dhcp4_option_def_AINS$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_option_def', NEW.id, 'create');
+ RETURN NULL;
+END;
+$dhcp4_option_def_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_option_def_AINS
+ AFTER INSERT ON dhcp4_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_option_def_AINS();
+
+-- Trigger function for dhcp4_option_def_AUPD called AFTER UPDATE on dhcp4_option_def
+CREATE OR REPLACE FUNCTION func_dhcp4_option_def_AUPD() RETURNS TRIGGER AS $dhcp4_option_def_AUPD$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_option_def', NEW.id, 'update');
+ RETURN NULL;
+END;
+$dhcp4_option_def_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_option_def_AUPD
+ AFTER UPDATE ON dhcp4_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_option_def_AUPD();
+
+-- Trigger function for dhcp4_option_def_ADEL called AFTER DELETE on dhcp4_option_def
+CREATE OR REPLACE FUNCTION func_dhcp4_option_def_ADEL() RETURNS TRIGGER AS $dhcp4_option_def_ADEL$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_option_def', OLD.id, 'delete');
+ RETURN NULL;
+END;
+$dhcp4_option_def_ADEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_option_def_ADEL
+ AFTER DELETE ON dhcp4_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_option_def_ADEL();
+
+-- -----------------------------------------------------
+--
+-- Stored procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: 'create', 'update' or 'delete'
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc.
+-- - option_id: identifier of the option.
+-- - p_subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - p_modification_ts: modification timestamp of the
+-- option.
+-- Some arguments are prefixed with "p_" to avoid ambiguity
+-- with column names in SQL statements. PostgreSQL does not
+-- allow table aliases to be used with column names in update
+-- set expressions.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP4(modification_type VARCHAR,
+ scope_id SMALLINT,
+ option_id INT,
+ p_subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR,
+ pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid VARCHAR(128);
+ sid BIGINT;
+ cascade_transaction BOOLEAN := true;
+ ct TEXT;
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp4_options' is when a global option is added.
+ -- Global options do not have the owner.
+
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp4_options' table.
+ PERFORM createAuditEntryDHCP4('dhcp4_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, update
+ -- the modification timestamp of this subnet to allow the
+ -- servers to refresh the subnet information. This will
+ -- also result in creating an audit entry for this subnet.
+ UPDATE dhcp4_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = p_subnet_id;
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- update the modification timestamp of this shared network
+ -- to allow the servers to refresh the shared network
+ -- information. This will also result in creating an
+ -- audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp4_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp4_shared_network SET modification_ts = p_modification_ts
+ WHERE id = snid;
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, update
+ -- the modification timestamp of the owning subnet.
+ SELECT dhcp4_pool.subnet_id INTO sid FROM dhcp4_pool WHERE id = pool_id;
+ UPDATE dhcp4_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ END IF;
+ END IF;
+ RETURN;
+END;$$;
+
+-- Trigger function for dhcp4_options_AINS called AFTER INSERT on dhcp4_options
+CREATE OR REPLACE FUNCTION func_dhcp4_options_AINS() RETURNS TRIGGER AS $dhcp4_options_AINS$
+BEGIN
+ PERFORM createOptionAuditDHCP4('create', NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.modification_ts);
+ RETURN NULL;
+END;
+$dhcp4_options_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_options_AINS
+ AFTER INSERT ON dhcp4_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_options_AINS();
+
+
+-- Trigger function for dhcp4_options_AUPD called AFTER UPDATE on dhcp4_options
+CREATE OR REPLACE FUNCTION func_dhcp4_options_AUPD() RETURNS TRIGGER AS $dhcp4_options_AUPD$
+BEGIN
+ PERFORM createOptionAuditDHCP4('update', NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.modification_ts);
+ RETURN NULL;
+END;
+$dhcp4_options_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_options_AUPD
+ AFTER UPDATE ON dhcp4_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_options_AUPD();
+
+-- Trigger function for dhcp4_options_ADEL called AFTER DELETE on dhcp4_options
+CREATE OR REPLACE FUNCTION func_dhcp4_options_ADEL() RETURNS TRIGGER AS $dhcp4_options_ADEL$
+BEGIN
+ PERFORM createOptionAuditDHCP4('delete', OLD.scope_id, OLD.option_id, OLD.dhcp4_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id,
+ NOW());
+ RETURN NULL;
+END;
+$dhcp4_options_ADEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_options_ADEL
+ AFTER DELETE ON dhcp4_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_options_ADEL();
+
+-- Trigger function for dhcp6_global_parameter_AINS called AFTER INSERT on dhcp6_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp6_global_parameter_AINS() RETURNS TRIGGER AS $dhcp6_global_parameter_AINS$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_global_parameter', NEW.id, 'create');
+ RETURN NULL;
+END;
+$dhcp6_global_parameter_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_global_parameter_AINS
+ AFTER INSERT ON dhcp6_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_global_parameter_AINS();
+
+-- Trigger function for dhcp6_global_parameter_AUPD called AFTER UPDATE on dhcp6_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp6_global_parameter_AUPD() RETURNS TRIGGER AS $dhcp6_global_parameter_AUPD$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_global_parameter', NEW.id, 'update');
+ RETURN NULL;
+END;
+$dhcp6_global_parameter_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_global_parameter_AUPD
+ AFTER UPDATE ON dhcp6_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_global_parameter_AUPD();
+
+-- Trigger function for dhcp6_global_parameter_ADEL called AFTER DELETE on dhcp6_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp6_global_parameter_ADEL() RETURNS TRIGGER AS $dhcp6_global_parameter_ADEL$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_global_parameter', OLD.id, 'delete');
+ RETURN NULL;
+END;
+$dhcp6_global_parameter_ADEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_global_parameter_ADEL
+ AFTER DELETE ON dhcp6_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_global_parameter_ADEL();
+
+-- Trigger function for dhcp6_subnet_AINS called AFTER INSERT on dhcp6_subnet
+CREATE OR REPLACE FUNCTION func_dhcp6_subnet_AINS() RETURNS TRIGGER AS $dhcp6_subnet_AINS$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', NEW.subnet_id, 'create');
+ RETURN NULL;
+END;
+$dhcp6_subnet_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_subnet_AINS
+ AFTER INSERT ON dhcp6_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_subnet_AINS();
+
+-- Trigger function for dhcp6_subnet_AUPD called AFTER UPDATE on dhcp6_subnet
+CREATE OR REPLACE FUNCTION func_dhcp6_subnet_AUPD() RETURNS TRIGGER AS $dhcp6_subnet_AUPD$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', NEW.subnet_id, 'update');
+ RETURN NULL;
+END;
+$dhcp6_subnet_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_subnet_AUPD
+ AFTER UPDATE ON dhcp6_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_subnet_AUPD();
+
+-- Trigger function for dhcp6_shared_network_AINS called AFTER INSERT on dhcp6_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp6_shared_network_AINS() RETURNS TRIGGER AS $dhcp6_shared_network_AINS$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', NEW.id, 'create');
+ RETURN NULL;
+END;
+$dhcp6_shared_network_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_shared_network_AINS
+ AFTER INSERT ON dhcp6_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_shared_network_AINS();
+
+-- Trigger function for dhcp6_shared_network_AUPD called AFTER UPDATE on dhcp6_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp6_shared_network_AUPD() RETURNS TRIGGER AS $dhcp6_shared_network_AUPD$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', NEW.id, 'update');
+ RETURN NULL;
+END;
+$dhcp6_shared_network_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_shared_network_AUPD
+ AFTER UPDATE ON dhcp6_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_shared_network_AUPD();
+
+-- Trigger function for dhcp6_shared_network_ADEL called AFTER DELETE on dhcp6_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp6_shared_network_ADEL() RETURNS TRIGGER AS $dhcp6_shared_network_ADEL$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, 'delete');
+ RETURN NULL;
+END;
+$dhcp6_shared_network_ADEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_shared_network_ADEL
+ AFTER DELETE ON dhcp6_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_shared_network_ADEL();
+
+-- Trigger function for dhcp6_option_def_AINS called AFTER INSERT on dhcp6_option_def
+CREATE OR REPLACE FUNCTION func_dhcp6_option_def_AINS() RETURNS TRIGGER AS $dhcp6_option_def_AINS$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_option_def', NEW.id, 'create');
+ RETURN NULL;
+END;
+$dhcp6_option_def_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_option_def_AINS
+ AFTER INSERT ON dhcp6_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_option_def_AINS();
+
+-- Trigger function for dhcp6_option_def_AUPD called AFTER UPDATE on dhcp6_option_def
+CREATE OR REPLACE FUNCTION func_dhcp6_option_def_AUPD() RETURNS TRIGGER AS $dhcp6_option_def_AUPD$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_option_def', NEW.id, 'update');
+ RETURN NULL;
+END;
+$dhcp6_option_def_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_option_def_AUPD
+ AFTER UPDATE ON dhcp6_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_option_def_AUPD();
+
+-- Trigger function for dhcp6_option_def_ADEL called AFTER DELETE on dhcp6_option_def
+CREATE OR REPLACE FUNCTION func_dhcp6_option_def_ADEL() RETURNS TRIGGER AS $dhcp6_option_def_ADEL$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_option_def', OLD.id, 'delete');
+ RETURN NULL;
+END;
+$dhcp6_option_def_ADEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_option_def_ADEL
+ AFTER DELETE ON dhcp6_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_option_def_ADEL();
+
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- - modification_ts: modification timestamp of the
+-- option.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP6(modification_type VARCHAR(32),
+ scope_id SMALLINT,
+ option_id INT,
+ subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR(128),
+ pool_id BIGINT,
+ pd_pool_id BIGINT,
+ modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid VARCHAR(128);
+ sid BIGINT;
+ cascade_transaction BOOLEAN := false;
+
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp6_options' is when a global option is added.
+ -- Global options do not have the owner.
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp6_options' table.
+ PERFORM createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, create
+ -- audit entry for the entire subnet, which indicates that
+ -- it should be treated as the subnet update.
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', subnet_id, 'update');
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- create audit entry for the shared network which
+ -- indicates that it should be treated as the shared
+ -- network update.
+ SELECT id INTO snid FROM dhcp6_shared_network
+ WHERE name = network_name LIMIT 1;
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', snid, 'update');
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, create
+ -- audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', sid, 'update');
+ ELSEIF scope_id = 6 THEN
+ -- If pd pool specific option is added or modified, create
+ -- audit entry for the subnet which this pd pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool
+ WHERE id = pd_pool_id;
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', sid, 'update');
+ END IF;
+ END IF;
+ RETURN;
+END;$$;
+
+-- Trigger function for dhcp6_options_AINS called AFTER INSERT on dhcp6_options
+CREATE OR REPLACE FUNCTION func_dhcp6_options_AINS() RETURNS TRIGGER AS $dhcp6_options_AINS$
+BEGIN
+ PERFORM createOptionAuditDHCP6('create', NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.pd_pool_id, NEW.modification_ts);
+ RETURN NULL;
+END;
+$dhcp6_options_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_options_AINS
+ AFTER INSERT ON dhcp6_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_options_AINS();
+
+-- Trigger function for dhcp6_options_AUPD called AFTER UPDATE on dhcp6_options
+CREATE OR REPLACE FUNCTION func_dhcp6_options_AUPD() RETURNS TRIGGER AS $dhcp6_options_AUPD$
+BEGIN
+ PERFORM createOptionAuditDHCP6('update', NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.pd_pool_id, NEW.modification_ts);
+ RETURN NULL;
+END;
+$dhcp6_options_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_options_AUPD
+ AFTER UPDATE ON dhcp6_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_options_AUPD();
+
+
+-- Trigger function for dhcp6_options_ADEL called AFTER DELETE on dhcp6_options
+CREATE OR REPLACE FUNCTION func_dhcp6_options_ADEL() RETURNS TRIGGER AS $dhcp6_options_ADEL$
+BEGIN
+ PERFORM createOptionAuditDHCP6('delete', OLD.scope_id, OLD.option_id, OLD.dhcp6_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id,
+ OLD.pd_pool_id, NOW());
+ RETURN NULL;
+END;
+$dhcp6_options_ADEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_options_ADEL
+ AFTER DELETE ON dhcp6_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_options_ADEL();
+
+
+-- Trigger function for dhcp4_server_AINS called AFTER INSERT on dhcp4_server
+CREATE OR REPLACE FUNCTION func_dhcp4_server_AINS() RETURNS TRIGGER AS $dhcp4_server_AINS$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_server', NEW.id, 'create');
+ RETURN NULL;
+END;
+$dhcp4_server_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_server_AINS
+ AFTER INSERT ON dhcp4_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_server_AINS();
+
+
+-- Trigger function for dhcp4_server_AUPD called AFTER UPDATE on dhcp4_server
+CREATE OR REPLACE FUNCTION func_dhcp4_server_AUPD() RETURNS TRIGGER AS $dhcp4_server_AUPD$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_server', NEW.id, 'update');
+ RETURN NULL;
+END;
+$dhcp4_server_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_server_AUPD
+ AFTER UPDATE ON dhcp4_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_server_AUPD();
+
+
+-- Trigger function for dhcp4_server_ADEL called AFTER DELETE on dhcp4_server
+CREATE OR REPLACE FUNCTION func_dhcp4_server_ADEL() RETURNS TRIGGER AS $dhcp4_server_ADEL$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_server', OLD.id, 'delete');
+ RETURN NULL;
+END;
+$dhcp4_server_ADEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_server_ADEL
+ AFTER DELETE ON dhcp4_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_server_ADEL();
+
+
+-- Trigger function for dhcp6_server_AINS called AFTER INSERT on dhcp6_server
+CREATE OR REPLACE FUNCTION func_dhcp6_server_AINS() RETURNS TRIGGER AS $dhcp6_server_AINS$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_server', NEW.id, 'create');
+ RETURN NULL;
+END;
+$dhcp6_server_AINS$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_server_AINS
+ AFTER INSERT ON dhcp6_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_server_AINS();
+
+
+-- Trigger function for dhcp6_server_AUPD called AFTER UPDATE on dhcp6_server
+CREATE OR REPLACE FUNCTION func_dhcp6_server_AUPD() RETURNS TRIGGER AS $dhcp6_server_AUPD$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_server', NEW.id, 'update');
+ RETURN NULL;
+END;
+$dhcp6_server_AUPD$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_server_AUPD
+ AFTER UPDATE ON dhcp6_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_server_AUPD();
+
+-- Trigger function for dhcp6_server_ADEL called AFTER DELETE on dhcp6_server
+CREATE OR REPLACE FUNCTION func_dhcp6_server_ADEL() RETURNS TRIGGER AS $dhcp6_server_ADEL$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_server', OLD.id, 'delete');
+ RETURN NULL;
+END;
+$dhcp6_server_ADEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_server_ADEL
+ AFTER DELETE ON dhcp6_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_server_ADEL();
+
+-- Trigger function for dhcp4_shared_network_BDEL called BEFORE DELETE on dhcp4_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp4_shared_network_BDEL() RETURNS TRIGGER AS $dhcp4_shared_network_BDEL$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, 'delete');
+ DELETE FROM dhcp4_options WHERE shared_network_name = OLD.name;
+ RETURN OLD;
+END;
+$dhcp4_shared_network_BDEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_shared_network_BDEL
+ BEFORE DELETE ON dhcp4_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_shared_network_BDEL();
+
+-- Trigger function for dhcp4_subnet_BDEL called BEFORE DELETE on dhcp4_subnet
+CREATE OR REPLACE FUNCTION func_dhcp4_subnet_BDEL() RETURNS TRIGGER AS $dhcp4_subnet_BDEL$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_subnet', OLD.subnet_id, 'delete');
+ DELETE FROM dhcp4_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp4_options WHERE dhcp4_subnet_id = OLD.subnet_id;
+ RETURN OLD;
+END;
+$dhcp4_subnet_BDEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_subnet_BDEL
+ BEFORE DELETE ON dhcp4_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_subnet_BDEL();
+
+-- Trigger function for dhcp6_shared_network_BDEL called BEFORE DELETE on dhcp6_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp6_shared_network_BDEL() RETURNS TRIGGER AS $dhcp6_shared_network_BDEL$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, 'delete');
+ DELETE FROM dhcp6_options WHERE shared_network_name = OLD.name;
+ RETURN OLD;
+END;
+$dhcp6_shared_network_BDEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_shared_network_BDEL
+ BEFORE DELETE ON dhcp6_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_shared_network_BDEL();
+
+-- Trigger function for dhcp6_subnet_BDEL called BEFORE DELETE on dhcp6_subnet
+CREATE OR REPLACE FUNCTION func_dhcp6_subnet_BDEL() RETURNS TRIGGER AS $dhcp6_subnet_BDEL$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', OLD.subnet_id, 'delete');
+ DELETE FROM dhcp6_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp6_pd_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp6_options WHERE dhcp6_subnet_id = OLD.subnet_id;
+ RETURN OLD;
+END;
+$dhcp6_subnet_BDEL$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_subnet_BDEL
+ BEFORE DELETE ON dhcp6_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_subnet_BDEL();
+-- Trigger function for dhcp6_pd_pool_BDEL called BEFORE DELETE on dhcp6_pd_pool
+CREATE OR REPLACE FUNCTION func_dhcp6_pd_pool_BDEL() RETURNS TRIGGER AS $dhcp6_pd_pool_BDEL$
+BEGIN
+ DELETE FROM dhcp6_options WHERE scope_id = 6 AND pd_pool_id = OLD.id;
+ RETURN OLD;
+END;
+$dhcp6_pd_pool_BDEL$
+LANGUAGE plpgsql;
+
+-- Update the schema version number
+UPDATE schema_version
+ SET version = '7', minor = '0';
+
+-- Schema 7.0 specification ends here.
+
+-- This starts schema update to 8.0. It adds a few missing elements for CB and
+-- functions for kea-admin's lease-dump and lease-upload commands.
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv4 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp4_option_def
+ ADD COLUMN class_id BIGINT NULL DEFAULT NULL;
+
+ALTER TABLE dhcp4_option_def
+ ADD CONSTRAINT fk_dhcp4_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv6 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp6_option_def
+ ADD COLUMN class_id BIGINT NULL DEFAULT NULL;
+
+ALTER TABLE dhcp6_option_def
+ ADD CONSTRAINT fk_dhcp6_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+-- -----------------------------------------------------------------------
+-- Add missing preferred_lifetime columns to dhcp6_client_class table.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp6_client_class
+ ADD COLUMN preferred_lifetime BIGINT DEFAULT NULL,
+ ADD COLUMN min_preferred_lifetime BIGINT DEFAULT NULL,
+ ADD COLUMN max_preferred_lifetime BIGINT DEFAULT NULL;
+
+-- -----------------------------------------------------------------------
+-- Add option scopes
+-- -----------------------------------------------------------------------
+-- Add scope for shared network specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(4, 'shared-network');
+
+-- Add scope for pool specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(5, 'pool');
+
+-- Add scope for PD pool specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(6, 'pd-pool');
+
+-- Drop the existing function, createOptionAuditDHCP6 so we can replace it
+-- with one that has slightly different arguments.
+DROP FUNCTION IF EXISTS createOptionAuditDHCP6(modification_type VARCHAR(32),
+ scope_id SMALLINT, option_id INT, subnet_id BIGINT,
+ host_id INT, network_name VARCHAR(128),
+ pool_id BIGINT, pd_pool_id BIGINT,
+ modification_ts TIMESTAMP WITH TIME ZONE);
+
+-- -----------------------------------------------------
+--
+-- New version of the createOptionAuditDHCP6 stored
+-- procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- - modification_ts: modification timestamp of the
+-- option.
+-- Some arguments are prefixed with "p_" to avoid ambiguity
+-- with column names in SQL statements. PostgreSQL does not
+-- allow table aliases to be used with column names in update
+-- set expressions.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP6(modification_type VARCHAR(32),
+ scope_id SMALLINT,
+ option_id INT,
+ p_subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR(128),
+ pool_id BIGINT,
+ pd_pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid VARCHAR(128);
+ sid BIGINT;
+ cascade_transaction BOOLEAN := false;
+
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp6_options' is when a global option is added.
+ -- Global options do not have the owner.
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp6_options' table.
+ PERFORM createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, update
+ -- the modification timestamp of this subnet to allow the
+ -- servers to refresh the subnet information. This will
+ -- also result in creating an audit entry for this subnet.
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = p_subnet_id;
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- update the modification timestamp of this shared network
+ -- to allow the servers to refresh the shared network
+ -- information. This will also result in creating an
+ -- audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp6_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp6_shared_network SET modification_ts = p_modification_ts
+ WHERE id = snid;
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, update
+ -- the modification timestamp of the owning subnet.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ ELSEIF scope_id = 6 THEN
+ -- If pd pool specific option is added or modified, create
+ -- audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ END IF;
+ END IF;
+ RETURN;
+END;$$;
+
+-- Fix mangled constraints on dhcp4_subnet_server table.
+ALTER TABLE dhcp4_subnet_server
+ DROP CONSTRAINT fk_dhcp6_subnet_server_server_id,
+ ADD CONSTRAINT fk_dhcp4_subnet_server_server_id
+ FOREIGN KEY (server_id) REFERENCES dhcp4_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ DROP CONSTRAINT fk_dhcp6_subnet_server_subnet_id,
+ ADD CONSTRAINT fk_dhcp4_subnet_server_subnet_id
+ FOREIGN KEY (subnet_id) REFERENCES dhcp4_subnet (subnet_id) ON DELETE CASCADE ON UPDATE NO ACTION;
+
+-- Add missing foreign key indexes. PostgreSQL does not automatically create indexes for
+-- foreign key constraints. These have been added using the basic guideline:
+--
+-- If the constraint does not reference a static table (e.g. parameter_data_type),
+-- and the referencing column is not the primary key or the first
+-- column in the primary key, and does not already have an index, then an index
+-- should be added to the table for the referencing column.
+--
+-- dhcp6_global_parameter_server
+CREATE INDEX fk_dhcp6_global_parameter_server_server_id ON dhcp6_global_parameter_server (server_id);
+
+-- dhcp6_options
+-- Missing foreign key constraint and indexes
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_subnet
+ FOREIGN KEY (dhcp6_subnet_id)
+ REFERENCES dhcp6_subnet(subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+CREATE INDEX fk_dhcp6_options_subnet ON dhcp6_options (dhcp6_subnet_id);
+CREATE INDEX fk_dhcp6_options_pd_pool ON dhcp6_options (pd_pool_id);
+CREATE INDEX fk_dhcp6_options_pool ON dhcp6_options (pool_id);
+CREATE INDEX fk_dhcp6_options_shared_network ON dhcp6_options (shared_network_name);
+
+-- dhcp6_option_def_server
+-- Missing foreign key constraints and index
+ALTER TABLE dhcp6_option_def_server
+ ADD CONSTRAINT fk_dhcp6_option_def_server_option_def_id FOREIGN KEY (option_def_id)
+ REFERENCES dhcp6_option_def (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ ADD CONSTRAINT fk_dhcp6_option_def_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+CREATE INDEX fk_dhcp6_option_def_server_server_id ON dhcp6_option_def_server (server_id);
+
+-- dhcp6_option_def
+CREATE INDEX fk_dhcp6_option_def_client_class_id ON dhcp6_option_def (class_id);
+
+-- dhcp4_global_parameter_server
+CREATE INDEX fk_dhcp4_global_parameter_server_server_id ON dhcp4_global_parameter_server (server_id);
+
+-- dhcp4_options
+-- Missing foreign key constraint and indexes
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_subnet
+ FOREIGN KEY (dhcp4_subnet_id)
+ REFERENCES dhcp4_subnet(subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+CREATE INDEX fk_dhcp4_options_subnet ON dhcp4_options (dhcp4_subnet_id);
+CREATE INDEX fk_dhcp4_options_pool ON dhcp4_options (pool_id);
+CREATE INDEX fk_dhcp4_options_shared_network ON dhcp4_options (shared_network_name);
+
+-- dhcp4_option_def_server
+-- Missing foreign key constraints and index
+ALTER TABLE dhcp4_option_def_server
+ ADD CONSTRAINT fk_dhcp4_option_def_server_option_def_id FOREIGN KEY (option_def_id)
+ REFERENCES dhcp4_option_def (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ ADD CONSTRAINT fk_dhcp4_option_def_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+CREATE INDEX fk_dhcp4_option_def_server_server_id ON dhcp4_option_def_server (server_id);
+
+-- dhcp4_option_def
+CREATE INDEX fk_dhcp4_option_def_client_class_id ON dhcp4_option_def (class_id);
+
+-- Create a function that separates groups of two hexadecimals
+-- with colons.
+CREATE OR REPLACE FUNCTION colonSeparatedHex(hex TEXT)
+RETURNS TEXT
+AS $$
+DECLARE
+ i INT := 3;
+ length INT := LENGTH(hex);
+ output TEXT;
+BEGIN
+ -- Add a leading zero if the first octet has a single hexadecimal character.
+ IF MOD(length, 2) = 1 THEN
+ hex := CONCAT('0', hex);
+ length := length + 1;
+ END IF;
+
+ -- Start with the first octet.
+ output := SUBSTR(hex, 1, 2);
+
+ -- Add one octet at a time and a leading colon with each.
+ WHILE i < length LOOP
+ output := CONCAT(output, ':', SUBSTR(hex, i, 2));
+ i := i + 2;
+ END LOOP;
+
+ -- Memfile uses lowercase hexadecimals.
+ output := LOWER(output);
+
+ RETURN output;
+END
+$$ LANGUAGE plpgsql;
+
+-- Modify the function to output a memfile-ready CSV file.
+-- Some columns that are SMALLINT in the lease4 table have their type promoted
+-- to INT in the declaration of this function for backwards compatibility with
+-- PostgreSQL versions.
+DROP FUNCTION IF EXISTS lease4DumpData();
+CREATE OR REPLACE FUNCTION lease4DumpData()
+RETURNS TABLE (
+ address INET,
+ hwaddr VARCHAR,
+ client_id VARCHAR,
+ valid_lifetime BIGINT,
+ expire BIGINT,
+ subnet_id BIGINT,
+ fqdn_fwd INT,
+ fqdn_rev INT,
+ hostname VARCHAR,
+ state INT8,
+ user_context VARCHAR
+) AS $$
+ SELECT
+ ('0.0.0.0'::inet + address),
+ colonSeparatedHex(encode(hwaddr, 'hex')),
+ colonSeparatedHex(encode(client_id, 'hex')),
+ valid_lifetime,
+ extract(epoch from expire)::bigint,
+ subnet_id,
+ fqdn_fwd::int,
+ fqdn_rev::int,
+ replace(hostname, ',', '&#x2c'),
+ state,
+ replace(user_context, ',', '&#x2c')
+ FROM lease4
+ ORDER BY address;
+$$ LANGUAGE SQL;
+
+-- hwtype and hwaddr_source need to be last to match memfile format.
+DROP FUNCTION IF EXISTS lease6DumpHeader();
+CREATE OR REPLACE FUNCTION lease6DumpHeader()
+RETURNS TEXT AS $$
+ SELECT CAST('address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state,user_context,hwtype,hwaddr_source' AS TEXT) AS result;
+$$ LANGUAGE SQL;
+
+-- Modify the function to output a memfile-ready CSV file.
+-- Some columns that are SMALLINT in the lease6 table have their type promoted
+-- to INT in the declaration of this function for backwards compatibility with
+-- PostgreSQL versions.
+DROP FUNCTION IF EXISTS lease6DumpData();
+CREATE OR REPLACE FUNCTION lease6DumpData()
+RETURNS TABLE (
+ address VARCHAR,
+ duid VARCHAR,
+ valid_lifetime BIGINT,
+ expire BIGINT,
+ subnet_id BIGINT,
+ pref_lifetime BIGINT,
+ lease_type SMALLINT,
+ iaid INT,
+ prefix_len SMALLINT,
+ fqdn_fwd INT,
+ fqdn_rev INT,
+ hostname VARCHAR,
+ hwaddr VARCHAR,
+ state INT8,
+ user_context VARCHAR,
+ hwtype SMALLINT,
+ hwaddr_source SMALLINT
+) AS $$
+ SELECT
+ address,
+ colonSeparatedHex(encode(duid, 'hex')),
+ valid_lifetime,
+ extract(epoch from expire)::bigint,
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd::int,
+ fqdn_rev::int,
+ replace(hostname, ',', '&#x2c'),
+ colonSeparatedHex(encode(hwaddr, 'hex')),
+ state,
+ replace(user_context, ',', '&#x2c'),
+ hwtype,
+ hwaddr_source
+ FROM lease6
+ ORDER BY address;
+$$ LANGUAGE SQL;
+
+-- Create a procedure that inserts a v4 lease from memfile data.
+-- Some columns that are SMALLINT in the lease4 table have their type promoted
+-- to INT in the declaration of this function for backwards compatibility with
+-- PostgreSQL versions.
+CREATE OR REPLACE FUNCTION lease4Upload(
+ IN address VARCHAR,
+ IN hwaddr VARCHAR,
+ IN client_id VARCHAR,
+ IN valid_lifetime BIGINT,
+ IN expire BIGINT,
+ IN subnet_id BIGINT,
+ IN fqdn_fwd INT,
+ IN fqdn_rev INT,
+ IN hostname VARCHAR,
+ IN state INT8,
+ IN user_context VARCHAR
+) RETURNS VOID AS $$
+BEGIN
+ INSERT INTO lease4 (
+ address,
+ hwaddr,
+ client_id,
+ valid_lifetime,
+ expire,
+ subnet_id,
+ fqdn_fwd,
+ fqdn_rev,
+ hostname,
+ state,
+ user_context
+ ) VALUES (
+ address::inet - '0.0.0.0'::inet,
+ decode(replace(hwaddr, ':', ''), 'hex'),
+ decode(replace(client_id, ':', ''), 'hex'),
+ valid_lifetime,
+ to_timestamp(expire),
+ subnet_id,
+ fqdn_fwd::int::boolean,
+ fqdn_rev::int::boolean,
+ replace(hostname, '&#x2c', ','),
+ state,
+ replace(user_context, '&#x2c', ',')
+ );
+END
+$$ LANGUAGE plpgsql;
+
+-- Create a procedure that inserts a v6 lease from memfile data.
+-- Some columns that are SMALLINT in the lease6 table have their type promoted
+-- to INT in the declaration of this function for backwards compatibility with
+-- PostgreSQL versions.
+CREATE OR REPLACE FUNCTION lease6Upload(
+ IN address VARCHAR,
+ IN duid VARCHAR,
+ IN valid_lifetime BIGINT,
+ IN expire BIGINT,
+ IN subnet_id BIGINT,
+ IN pref_lifetime BIGINT,
+ IN lease_type INT,
+ IN iaid INT,
+ IN prefix_len INT,
+ IN fqdn_fwd INT,
+ IN fqdn_rev INT,
+ IN hostname VARCHAR,
+ IN hwaddr VARCHAR,
+ IN state INT8,
+ IN user_context VARCHAR,
+ IN hwtype INT,
+ IN hwaddr_source INT
+) RETURNS VOID AS $$
+BEGIN
+ INSERT INTO lease6 (
+ address,
+ duid,
+ valid_lifetime,
+ expire,
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd,
+ fqdn_rev,
+ hostname,
+ hwaddr,
+ state,
+ user_context,
+ hwtype,
+ hwaddr_source
+ ) VALUES (
+ address,
+ decode(replace(duid, ':', ''), 'hex'),
+ valid_lifetime,
+ to_timestamp(expire),
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd::int::boolean,
+ fqdn_rev::int::boolean,
+ replace(hostname, '&#x2c', ','),
+ decode(replace(hwaddr, ':', ''), 'hex'),
+ state,
+ replace(user_context, '&#x2c', ','),
+ hwtype,
+ hwaddr_source
+ );
+END
+$$ LANGUAGE plpgsql;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '8', minor = '0';
+
+-- Schema 8.0 specification ends here.
+
+-- This starts schema update to 9.0.
+
+-- Add missing cascade to constraint on dhcp4/6_subnet_server tables.
+ALTER TABLE dhcp4_subnet_server
+ DROP CONSTRAINT fk_dhcp4_subnet_server_server_id,
+ ADD CONSTRAINT fk_dhcp4_subnet_server_server_id
+ FOREIGN KEY (server_id) REFERENCES dhcp4_server (id) ON DELETE CASCADE ON UPDATE CASCADE,
+ DROP CONSTRAINT fk_dhcp4_subnet_server_subnet_id,
+ ADD CONSTRAINT fk_dhcp4_subnet_server_subnet_id
+ FOREIGN KEY (subnet_id) REFERENCES dhcp4_subnet (subnet_id) ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_subnet_server
+ DROP CONSTRAINT fk_dhcp6_subnet_server_server_id,
+ ADD CONSTRAINT fk_dhcp6_subnet_server_server_id
+ FOREIGN KEY (server_id) REFERENCES dhcp6_server (id) ON DELETE CASCADE ON UPDATE CASCADE,
+ DROP CONSTRAINT fk_dhcp6_subnet_server_subnet_id,
+ ADD CONSTRAINT fk_dhcp6_subnet_server_subnet_id
+ FOREIGN KEY (subnet_id) REFERENCES dhcp6_subnet (subnet_id) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- Fix constraint typo on dhcp4_option_def_server
+ALTER TABLE dhcp4_option_def_server
+ DROP CONSTRAINT dhcp4_option_def_server_option_def_id_fkey,
+ ADD CONSTRAINT dhcp4_option_def_server_option_def_id_fkey
+ FOREIGN KEY (option_def_id) REFERENCES dhcp4_option_def(id) ON DELETE CASCADE;
+
+-- DROP shared-network ADEL triggers that should not exist.
+DROP TRIGGER IF EXISTS dhcp4_shared_network_ADEL on dhcp4_shared_network CASCADE;
+DROP TRIGGER IF EXISTS dhcp6_shared_network_ADEL on dhcp6_shared_network CASCADE;
+
+-- Replace createOptionAuditDHCP4() with a version that has local variable
+-- snid correctly declared as a BIGINT.
+--
+-- -----------------------------------------------------
+--
+-- Stored procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - p_subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - p_modification_ts: modification timestamp of the
+-- option.
+-- Some arguments are prefixed with "p_" to avoid ambiguity
+-- with column names in SQL statements. PostgreSQL does not
+-- allow table aliases to be used with column names in update
+-- set expressions.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP4(modification_type VARCHAR,
+ scope_id SMALLINT,
+ option_id INT,
+ p_subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR,
+ pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid BIGINT;
+ sid BIGINT;
+ cascade_transaction BOOLEAN;
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp4_options' is when a global option is added.
+ -- Global options do not have the owner.
+
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp4_options' table.
+ PERFORM createAuditEntryDHCP4('dhcp4_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, update
+ -- the modification timestamp of this subnet to allow the
+ -- servers to refresh the subnet information. This will
+ -- also result in creating an audit entry for this subnet.
+ UPDATE dhcp4_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = p_subnet_id;
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- update the modification timestamp of this shared network
+ -- to allow the servers to refresh the shared network
+ -- information. This will also result in creating an
+ -- audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp4_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp4_shared_network SET modification_ts = p_modification_ts
+ WHERE id = snid;
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, update
+ -- the modification timestamp of the owning subnet.
+ SELECT dhcp4_pool.subnet_id INTO sid FROM dhcp4_pool WHERE id = pool_id;
+ UPDATE dhcp4_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ END IF;
+ END IF;
+ RETURN;
+END;$$;
+
+-- Replace createOptionAuditDHCP6() with a version that has local variable
+-- snid correctly declared as a BIGINT.
+--
+-- -----------------------------------------------------
+--
+-- Stored procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - p_subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- - p_modification_ts: modification timestamp of the
+-- option.
+-- Some arguments are prefixed with "p_" to avoid ambiguity
+-- with column names in SQL statements. PostgreSQL does not
+-- allow table aliases to be used with column names in update
+-- set expressions.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP6(modification_type VARCHAR,
+ scope_id SMALLINT,
+ option_id INT,
+ p_subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR,
+ pool_id BIGINT,
+ pd_pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid BIGINT;
+ sid BIGINT;
+ cascade_transaction BOOLEAN;
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp6_options' is when a global option is added.
+ -- Global options do not have the owner.
+
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp6_options' table.
+ PERFORM createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, update
+ -- the modification timestamp of this subnet to allow the
+ -- servers to refresh the subnet information. This will
+ -- also result in creating an audit entry for this subnet.
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = p_subnet_id;
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- update the modification timestamp of this shared network
+ -- to allow the servers to refresh the shared network
+ -- information. This will also result in creating an
+ -- audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp6_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp6_shared_network SET modification_ts = p_modification_ts
+ WHERE id = snid;
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, update
+ -- the modification timestamp of the owning subnet.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ ELSEIF scope_id = 6 THEN
+ -- If pd pool specific option is added or modified, create
+ -- audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ END IF;
+ END IF;
+ RETURN;
+END;$$;
+
+-- Returns the epoch GMT time in second from a timestamp with time zone
+--
+-- param input_ts timestamp value to convert
+-- return a BIGINT containing the number of seconds since the epoch in GMT.
+CREATE OR REPLACE FUNCTION gmt_epoch(input_ts TIMESTAMP WITH TIME ZONE)
+RETURNS BIGINT
+AS $$
+DECLARE
+ gmt_epoch BIGINT;
+BEGIN
+ SELECT (extract(epoch from input_ts) + extract(timezone from input_ts))::BIGINT INTO gmt_epoch;
+ RETURN gmt_epoch;
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'gmt_epoch(%) : failed, sqlstate: %', input_ts, sqlstate;
+END;$$
+LANGUAGE plpgsql;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '9', minor = '0';
+
+-- Schema 9.0 specification ends here.
+
+-- This starts schema update to 10.0.
+-- It adds corrections for client classes for CB
+
+-- Replace setClientClass4Order():
+-- 1. l_depend_on_known_indirectly needs to be BOOL
+-- 2. follow_class_index needs to be BIGINT
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- new_follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - new_follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass4Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BOOL := false;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp4_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp4_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp4_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp4_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp4_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency AS d
+ INNER JOIN dhcp4_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM - note that ON CONFLICT requires PostgreSQL 9.5 or later.
+ UPDATE dhcp4_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, false);
+ RETURN;
+END;$$;
+
+-- Replace setClientClass6Order():
+-- 1. l_depend_on_known_indirectly needs to be BOOL
+-- 2. follow_class_index needs to be BIGINT
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- new_follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - new_follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass6Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BOOL := false;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp6_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp6_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp6_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp6_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp6_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency AS d
+ INNER JOIN dhcp6_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM - note that ON CONFLICT requires PostgreSQL 9.5 or later.
+ UPDATE dhcp6_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, false);
+ RETURN;
+END;$$;
+
+-- Change primary key to composite, dependency table can have multiple rows
+-- per class id.
+ALTER TABLE dhcp4_client_class_dependency DROP CONSTRAINT dhcp4_client_class_dependency_pkey;
+ALTER TABLE dhcp4_client_class_dependency ADD PRIMARY KEY(class_id, dependency_id);
+
+ALTER TABLE dhcp6_client_class_dependency DROP CONSTRAINT dhcp6_client_class_dependency_pkey;
+ALTER TABLE dhcp6_client_class_dependency ADD PRIMARY KEY(class_id, dependency_id);
+
+-- Replace triggers that verify class dependency.
+-- Because they are BEFORE INSERT triggers they need to return NEW not NULL.
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_check_dependency_BINS()
+ RETURNS trigger AS $dhcp4_client_class_check_dependency_BINS$
+BEGIN
+ PERFORM checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NEW;
+END;
+$dhcp4_client_class_check_dependency_BINS$
+LANGUAGE plpgsql;
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_check_dependency_BINS()
+ RETURNS trigger AS $dhcp6_client_class_check_dependency_BINS$
+BEGIN
+ PERFORM checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NEW;
+END;
+$dhcp6_client_class_check_dependency_BINS$
+LANGUAGE plpgsql;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '10', minor = '0';
+
+-- Schema 10.0 specification ends here.
+
+-- This starts schema update to 11.0.
+
+-- Replace createOptionAuditDHCP6() with a version corrected
+-- where clause when scope is 6 (i.e. PD pool)
+--
+-- -----------------------------------------------------
+--
+-- Stored procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - p_subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- - p_modification_ts: modification timestamp of the
+-- option.
+-- Some arguments are prefixed with "p_" to avoid ambiguity
+-- with column names in SQL statements. PostgreSQL does not
+-- allow table aliases to be used with column names in update
+-- set expressions.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP6(modification_type VARCHAR,
+ scope_id SMALLINT,
+ option_id INT,
+ p_subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR,
+ pool_id BIGINT,
+ pd_pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid BIGINT;
+ sid BIGINT;
+ cascade_transaction BOOLEAN;
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp6_options' is when a global option is added.
+ -- Global options do not have the owner.
+
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp6_options' table.
+ PERFORM createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, update
+ -- the modification timestamp of this subnet to allow the
+ -- servers to refresh the subnet information. This will
+ -- also result in creating an audit entry for this subnet.
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = p_subnet_id;
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- update the modification timestamp of this shared network
+ -- to allow the servers to refresh the shared network
+ -- information. This will also result in creating an
+ -- audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp6_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp6_shared_network SET modification_ts = p_modification_ts
+ WHERE id = snid;
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, update
+ -- the modification timestamp of the owning subnet.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ ELSEIF scope_id = 6 THEN
+ -- If pd pool specific option is added or modified, create
+ -- audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pd_pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ END IF;
+ END IF;
+ RETURN;
+END;$$;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '11', minor = '0';
+
+-- Schema 11.0 specification ends here.
+
+-- This line starts the schema upgrade to version 12.
+
+-- Modify shared-network-name foreign key constraint on dhcp4_subnet to not perform
+-- the update when the network is deleted the cascaded update will not execute
+-- dhcp4_subnet update trigger leaving the updated subnets without audit_entries.
+ALTER TABLE dhcp4_subnet
+ DROP CONSTRAINT fk_dhcp4_subnet_shared_network,
+ ADD CONSTRAINT fk_dhcp4_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network (name)
+ ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+-- Modify BEFORE delete trigger function on dhcp4_shared_network to explicitly
+-- update dhcp4_subnets. This ensures there are audit entries for updated
+-- subnets.
+-- Trigger function for dhcp4_shared_network_BDEL called BEFORE DELETE on dhcp4_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp4_shared_network_BDEL() RETURNS TRIGGER AS $dhcp4_shared_network_BDEL$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, 'delete');
+ -- Explicitly update subnets now rather than via foreign key constraint, this ensures the
+ -- audit entries for subnets will preceded that of the shared-network, keeping the order
+ -- of the entries the same as they are for MySQL.
+ UPDATE dhcp4_subnet SET shared_network_name = NULL WHERE shared_network_name = OLD.name;
+ DELETE FROM dhcp4_options WHERE shared_network_name = OLD.name;
+ RETURN OLD;
+END;
+$dhcp4_shared_network_BDEL$
+LANGUAGE plpgsql;
+
+-- Modify shared-network-name foreign key constraint on dhcp6_subnet to not perform
+-- the update when the network is deleted the cascaded update will not execute
+-- dhcp6_subnet update trigger leaving the updated subnets without audit_entries.
+ALTER TABLE dhcp6_subnet
+ DROP CONSTRAINT fk_dhcp6_subnet_shared_network,
+ ADD CONSTRAINT fk_dhcp6_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network (name)
+ ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+-- Modify BEFORE delete trigger function on dhcp6_shared_network to explicitly
+-- update dhcp6_subnets. This ensures there are audit entries for updated
+-- subnets.
+-- Trigger function for dhcp6_shared_network_BDEL called BEFORE DELETE on dhcp6_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp6_shared_network_BDEL() RETURNS TRIGGER AS $dhcp6_shared_network_BDEL$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, 'delete');
+ -- Explicitly update subnets now rather than via foreign key constraint, this ensures the
+ -- audit entries for subnets will preceded that of the shared-network, keeping the order
+ -- of the entries the same as they are for MySQL.
+ UPDATE dhcp6_subnet SET shared_network_name = NULL WHERE shared_network_name = OLD.name;
+ DELETE FROM dhcp6_options WHERE shared_network_name = OLD.name;
+ RETURN OLD;
+END;
+$dhcp6_shared_network_BDEL$
+LANGUAGE plpgsql;
+
+-- Add user_context column to client class tables.
+ALTER TABLE dhcp4_client_class ADD COLUMN user_context JSON DEFAULT NULL;
+ALTER TABLE dhcp6_client_class ADD COLUMN user_context JSON DEFAULT NULL;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '12', minor = '0';
+
+-- This line concludes the schema upgrade to version 12.
+
+-- This line starts the schema upgrade to version 13.
+
+-- JSON functions --
+
+-- Helper function that avoids a casting error when the string
+-- presumed to be in JSON format, is empty.
+CREATE OR REPLACE FUNCTION json_cast(IN json_candidate TEXT)
+RETURNS JSON
+AS $$
+BEGIN
+ IF LENGTH(json_candidate) = 0 THEN
+ RETURN '{}'::json;
+ END IF;
+ RETURN json_candidate::json;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Function that establishes whether JSON functions are supported.
+-- They should be provided with PostgreSQL >= 9.4.
+CREATE OR REPLACE FUNCTION isJsonSupported()
+RETURNS BOOLEAN
+AS $$
+BEGIN
+ IF get_session_value('json_supported') IS NULL THEN
+ IF (SELECT proname FROM pg_proc WHERE proname = 'json_extract_path') = 'json_extract_path' THEN
+ PERFORM set_session_value('kea.json_supported', true);
+ ELSE
+ PERFORM set_session_value('kea.json_supported', false);
+ END IF;
+ END IF;
+ RETURN get_session_value('kea.json_supported');
+END
+$$ LANGUAGE plpgsql;
+
+-- Schema changes related to lease limiting start here. --
+
+-- Recreate the triggers that update the leaseX_stat tables as stored procedures. --
+
+CREATE OR REPLACE FUNCTION lease4_AINS_lease4_stat(IN new_state BIGINT,
+ IN new_subnet_id BIGINT)
+RETURNS VOID
+AS $$
+BEGIN
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Update the state count if it exists.
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND state = new_state;
+
+ -- Insert the state count record if it does not exist.
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat VALUES (new_subnet_id, new_state, 1);
+ END IF;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease4_AUPD_lease4_stat(IN old_state BIGINT,
+ IN old_subnet_id BIGINT,
+ IN new_state BIGINT,
+ IN new_subnet_id BIGINT)
+RETURNS VOID
+AS $$
+BEGIN
+ IF old_subnet_id != new_subnet_id OR old_state != new_state THEN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the old state count if record exists.
+ UPDATE lease4_stat
+ SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND state = old_state;
+ END IF;
+
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Increment the new state count if record exists.
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND state = new_state;
+
+ -- Insert new state record if it does not exist.
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat VALUES (new_subnet_id, new_state, 1);
+ END IF;
+ END IF;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease4_ADEL_lease4_stat(IN old_state BIGINT,
+ IN old_subnet_id BIGINT)
+RETURNS VOID
+AS $$
+BEGIN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the state count if record exists.
+ UPDATE lease4_stat
+ SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND old_state = state;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_AINS_lease6_stat(IN new_state BIGINT,
+ IN new_subnet_id BIGINT,
+ IN new_lease_type SMALLINT)
+RETURNS VOID
+AS $$
+BEGIN
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Update the state count if it exists.
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND lease_type = new_lease_type
+ AND state = new_state;
+
+ -- Insert the state count record if it does not exist.
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat VALUES (new_subnet_id, new_lease_type, new_state, 1);
+ END IF;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_AUPD_lease6_stat(IN old_state BIGINT,
+ IN old_subnet_id BIGINT,
+ IN old_lease_type SMALLINT,
+ IN new_state BIGINT,
+ IN new_subnet_id BIGINT,
+ IN new_lease_type SMALLINT)
+RETURNS VOID
+AS $$
+BEGIN
+ IF old_subnet_id != new_subnet_id OR
+ old_lease_type != new_lease_type OR
+ old_state != new_state THEN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the old state count if record exists.
+ UPDATE lease6_stat
+ SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND lease_type = old_lease_type
+ AND state = old_state;
+ END IF;
+
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND lease_type = new_lease_type
+ AND state = new_state;
+
+ -- Insert new state record if it does not exist
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat
+ VALUES (new_subnet_id, new_lease_type, new_state, 1);
+ END IF;
+ END IF;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_ADEL_lease6_stat(IN old_state BIGINT,
+ IN old_subnet_id BIGINT,
+ IN old_lease_type SMALLINT)
+RETURNS VOID
+AS $$
+BEGIN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease6_stat
+ SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND lease_type = old_lease_type
+ AND state = old_state;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Create tables that contain the number of active leases. --
+
+CREATE TABLE lease4_stat_by_client_class (
+ client_class VARCHAR(128) NOT NULL PRIMARY KEY,
+ leases BIGINT NOT NULL
+);
+
+CREATE TABLE lease6_stat_by_client_class (
+ client_class VARCHAR(128) NOT NULL,
+ lease_type SMALLINT NOT NULL,
+ leases BIGINT NOT NULL,
+ PRIMARY KEY (client_class, lease_type),
+ CONSTRAINT fk_lease6_stat_by_client_class_lease_type FOREIGN KEY (lease_type)
+ REFERENCES lease6_types (lease_type)
+);
+
+-- Create procedures to be called for each row in after-event triggers for
+-- INSERT, UPDATE and DELETE on lease tables.
+
+CREATE OR REPLACE FUNCTION lease4_AINS_lease4_stat_by_client_class(IN new_state BIGINT,
+ IN new_user_context TEXT)
+RETURNS VOID
+AS $$
+DECLARE
+ class VARCHAR(128);
+BEGIN
+ -- Only state 0 is needed for lease limiting.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(new_user_context)->'ISC'->'client-classes') LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Upsert to increment the lease count.
+ UPDATE lease4_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class;
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat_by_client_class VALUES (class, 1);
+ END IF;
+ END LOOP;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease4_AUPD_lease4_stat_by_client_class(IN old_state BIGINT,
+ IN old_user_context TEXT,
+ IN new_state BIGINT,
+ IN new_user_context TEXT)
+RETURNS VOID
+AS $$
+DECLARE
+ old_client_classes TEXT;
+ new_client_classes TEXT;
+ class VARCHAR(128);
+ length INT;
+ i INT;
+BEGIN
+ SELECT json_cast(old_user_context)->'ISC'->'client-classes' INTO old_client_classes;
+ SELECT json_cast(new_user_context)->'ISC'->'client-classes' INTO new_client_classes;
+
+ IF old_state != new_state OR old_client_classes != new_client_classes THEN
+ -- Check if it's moving away from a counted state.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(old_client_classes)) LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease4_stat_by_client_class SET leases = GREATEST(leases - 1, 0)
+ WHERE client_class = class;
+ END LOOP;
+ END IF;
+
+ -- Check if it's moving into a counted state.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(new_client_classes)) LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Upsert to increment the lease count.
+ UPDATE lease4_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class;
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat_by_client_class VALUES (class, 1);
+ END IF;
+ END LOOP;
+ END IF;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease4_ADEL_lease4_stat_by_client_class(IN old_state BIGINT,
+ IN old_user_context TEXT)
+RETURNS VOID
+AS $$
+DECLARE
+ class VARCHAR(128);
+BEGIN
+ -- Only state 0 is accounted for in lease limiting.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(old_user_context)->'ISC'->'client-classes') LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease4_stat_by_client_class SET leases = GREATEST(leases - 1, 0)
+ WHERE client_class = class;
+ END LOOP;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_AINS_lease6_stat_by_client_class(IN new_state BIGINT,
+ IN new_user_context TEXT,
+ IN new_lease_type SMALLINT)
+RETURNS VOID
+AS $$
+DECLARE
+ client_classes TEXT;
+ class VARCHAR(128);
+ length INT;
+ i INT;
+BEGIN
+ -- Only state 0 is needed for lease limiting.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(new_user_context)->'ISC'->'client-classes') LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Upsert to increment the lease count.
+ UPDATE lease6_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class AND lease_type = new_lease_type;
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat_by_client_class VALUES (class, new_lease_type, 1);
+ END IF;
+ END LOOP;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_AUPD_lease6_stat_by_client_class(IN old_state BIGINT,
+ IN old_user_context TEXT,
+ IN old_lease_type SMALLINT,
+ IN new_state BIGINT,
+ IN new_user_context TEXT,
+ IN new_lease_type SMALLINT)
+RETURNS VOID
+AS $$
+DECLARE
+ old_client_classes TEXT;
+ new_client_classes TEXT;
+ class VARCHAR(128);
+ length INT;
+ i INT;
+BEGIN
+ SELECT json_cast(old_user_context)->'ISC'->'client-classes' INTO old_client_classes;
+ SELECT json_cast(new_user_context)->'ISC'->'client-classes' INTO new_client_classes;
+
+ IF old_state != new_state OR old_client_classes != new_client_classes OR old_lease_type != new_lease_type THEN
+ -- Check if it's moving away from a counted state.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(old_client_classes)) LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease6_stat_by_client_class SET leases = GREATEST(leases - 1, 0)
+ WHERE client_class = class AND lease_type = old_lease_type;
+ END LOOP;
+ END IF;
+
+ -- Check if it's moving into a counted state.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(new_client_classes)) LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Upsert to increment the lease count.
+ UPDATE lease6_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class AND lease_type = new_lease_type;
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat_by_client_class VALUES (class, new_lease_type, 1);
+ END IF;
+ END LOOP;
+ END IF;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_ADEL_lease6_stat_by_client_class(IN old_state BIGINT,
+ IN old_user_context TEXT,
+ IN old_lease_type SMALLINT)
+RETURNS VOID
+AS $$
+DECLARE
+ client_classes VARCHAR(1024);
+ class VARCHAR(128);
+ length INT;
+ i INT;
+BEGIN
+ -- Only state 0 is accounted for in lease limiting. But check both states to be consistent with lease6_stat.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(old_user_context)->'ISC'->'client-classes') LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease6_stat_by_client_class SET leases = GREATEST(leases - 1, 0)
+ WHERE client_class = class AND lease_type = old_lease_type;
+ END LOOP;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Recreate the after-event triggers for INSERT, UPDATE and DELETE on lease tables to call the --
+-- stored procedures above in pairs of two: for client classes and for subnets. --
+
+DROP TRIGGER IF EXISTS stat_lease4_insert ON lease4;
+
+CREATE OR REPLACE FUNCTION func_lease4_AINS()
+RETURNS trigger AS $lease4_AINS$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease4_AINS_lease4_stat_by_client_class(NEW.state, NEW.user_context);
+ END IF;
+ PERFORM lease4_AINS_lease4_stat(NEW.state, NEW.subnet_id);
+ RETURN NULL;
+END;
+$lease4_AINS$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease4_AINS AFTER INSERT ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE func_lease4_AINS();
+
+DROP TRIGGER IF EXISTS stat_lease4_update ON lease4;
+
+CREATE OR REPLACE FUNCTION func_lease4_AUPD()
+RETURNS trigger AS $lease4_AUPD$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease4_AUPD_lease4_stat_by_client_class(OLD.state, OLD.user_context, NEW.state, NEW.user_context);
+ END IF;
+ PERFORM lease4_AUPD_lease4_stat(OLD.state, OLD.subnet_id, NEW.state, NEW.subnet_id);
+ RETURN NULL;
+END;
+$lease4_AUPD$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease4_AUPD AFTER UPDATE ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE func_lease4_AUPD();
+
+DROP TRIGGER IF EXISTS stat_lease4_delete ON lease4;
+
+CREATE OR REPLACE FUNCTION func_lease4_ADEL()
+RETURNS trigger AS $lease4_ADEL$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease4_ADEL_lease4_stat_by_client_class(OLD.state, OLD.user_context);
+ END IF;
+ PERFORM lease4_ADEL_lease4_stat(OLD.state, OLD.subnet_id);
+ RETURN NULL;
+END;
+$lease4_ADEL$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease4_ADEL AFTER DELETE ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE func_lease4_ADEL();
+
+DROP TRIGGER IF EXISTS stat_lease6_insert ON lease6;
+
+CREATE OR REPLACE FUNCTION func_lease6_AINS()
+RETURNS trigger AS $lease6_AINS$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease6_AINS_lease6_stat_by_client_class(NEW.state, NEW.user_context, NEW.lease_type);
+ END IF;
+ PERFORM lease6_AINS_lease6_stat(NEW.state, NEW.subnet_id, NEW.lease_type);
+ RETURN NULL;
+END;
+$lease6_AINS$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease6_AINS AFTER INSERT ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE func_lease6_AINS();
+
+DROP TRIGGER IF EXISTS stat_lease6_update ON lease6;
+
+CREATE OR REPLACE FUNCTION func_lease6_AUPD()
+RETURNS trigger AS $lease6_AUPD$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease6_AUPD_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type, NEW.state, NEW.user_context, NEW.lease_type);
+ END IF;
+ PERFORM lease6_AUPD_lease6_stat(OLD.state, OLD.subnet_id, OLD.lease_type, NEW.state, NEW.subnet_id, NEW.lease_type);
+ RETURN NULL;
+END;
+$lease6_AUPD$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease6_AUPD AFTER UPDATE ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE func_lease6_AUPD();
+
+DROP TRIGGER IF EXISTS stat_lease6_delete ON lease6;
+
+CREATE OR REPLACE FUNCTION func_lease6_ADEL()
+RETURNS trigger AS $lease6_ADEL$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease6_ADEL_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type);
+ END IF;
+ PERFORM lease6_ADEL_lease6_stat(OLD.state, OLD.subnet_id, OLD.lease_type);
+ RETURN NULL;
+END;
+$lease6_ADEL$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease6_ADEL AFTER DELETE ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE func_lease6_ADEL();
+
+-- Create functions that return an empty TEXT if all limits allow for more leases, or otherwise a
+-- TEXT in one of the following JSON formats detailing the limit that was reached:
+-- { "limit-type": "client-class", "name": foo, "lease-type": "address", "limit": 2, "count": 2 }
+-- { "limit-type": "subnet", "id": 1, "lease-type": "IA_PD", "limit": 2, "count": 2 }
+-- The following format for user_context is assumed:
+-- { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+-- "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+
+CREATE OR REPLACE FUNCTION checkLease4Limits(user_context TEXT)
+RETURNS TEXT
+AS $$
+DECLARE
+ class TEXT;
+ name VARCHAR(255);
+ sid INT;
+ lease_limit INT;
+ lease_count INT;
+BEGIN
+ -- Dive into client class limits.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(user_context)->'ISC'->'limits'->'client-classes') LOOP
+ SELECT TRIM('"' FROM (json_cast(class)->'name')::text) INTO name;
+ SELECT json_cast(class)->'address-limit' INTO lease_limit;
+
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SELECT leases FROM lease4_stat_by_client_class INTO lease_count WHERE client_class = name;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+ END LOOP;
+
+ -- Dive into subnet limits.
+ SELECT json_cast(user_context)->'ISC'->'limits'->'subnet'->'id' INTO sid;
+ SELECT json_cast(user_context)->'ISC'->'limits'->'subnet'->'address-limit' INTO lease_limit;
+
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SELECT leases FROM lease4_stat WHERE subnet_id = sid AND state = 0 INTO lease_count;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for subnet ID ', sid, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ RETURN '';
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION checkLease6Limits(user_context TEXT)
+RETURNS TEXT
+AS $$
+DECLARE
+ class TEXT;
+ name VARCHAR(255);
+ sid INT;
+ lease_limit INT;
+ lease_count INT;
+BEGIN
+ -- Dive into client class limits.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(user_context)->'ISC'->'limits'->'client-classes') LOOP
+ SELECT TRIM('"' FROM (json_cast(class)->'name')::text) INTO name;
+ SELECT json_cast(class)->'address-limit' INTO lease_limit;
+
+ IF lease_limit IS NOT NULL THEN
+ -- Get the address count for this client class.
+ SELECT leases FROM lease6_stat_by_client_class WHERE client_class = name AND lease_type = 0 INTO lease_count;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ SELECT json_cast(class)->'prefix-limit' INTO lease_limit;
+ IF lease_limit IS NOT NULL THEN
+ -- Get the prefix count for this client class.
+ SELECT leases FROM lease6_stat_by_client_class WHERE client_class = name AND lease_type = 2 INTO lease_count;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('prefix limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+ END LOOP;
+
+ -- Dive into subnet limits.
+ SELECT json_cast(user_context)->'ISC'->'limits'->'subnet'->'id' INTO sid;
+ SELECT json_cast(user_context)->'ISC'->'limits'->'subnet'->'address-limit' INTO lease_limit;
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this subnet.
+ SELECT leases FROM lease6_stat WHERE subnet_id = sid AND lease_type = 0 AND state = 0 INTO lease_count;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for subnet ID ', sid, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+ SELECT json_cast(user_context)->'ISC'->'limits'->'subnet'->'prefix-limit' INTO lease_limit;
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SELECT leases FROM lease6_stat WHERE subnet_id = sid AND lease_type = 2 AND state = 0 INTO lease_count;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('prefix limit ', lease_limit, ' for subnet ID ', sid, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ RETURN '';
+END;
+$$ LANGUAGE plpgsql;
+
+-- Improve hosts indexes for better performance of global reservations
+-- Create new index that uses only dhcp_identifier.
+CREATE INDEX key_dhcp_identifier on hosts (dhcp_identifier, dhcp_identifier_type);
+
+-- Modify existing indexes to include subnet_id values of 0, so index is also used
+-- for global reservations.
+DROP INDEX IF EXISTS key_dhcp4_identifier_subnet_id;
+CREATE UNIQUE INDEX key_dhcp4_identifier_subnet_id ON hosts
+ (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp4_subnet_id ASC)
+ WHERE (dhcp4_subnet_id IS NOT NULL);
+
+DROP INDEX IF EXISTS key_dhcp6_identifier_subnet_id;
+CREATE UNIQUE INDEX key_dhcp6_identifier_subnet_id ON hosts
+ (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp6_subnet_id ASC)
+ WHERE (dhcp6_subnet_id IS NOT NULL);
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '13', minor = '0';
+
+-- This line concludes the schema upgrade to version 13.
+
+-- Commit the script transaction.
+COMMIT;
+
+-- Notes:
+
+-- Indexes
+-- =======
+-- It is likely that additional indexes will be needed. However, the
+-- increase in lookup performance from these will come at the expense
+-- of a decrease in performance during insert operations due to the need
+-- to update the indexes. For this reason, the need for additional indexes
+-- will be determined by experiment during performance tests.
+
+-- The most likely additional indexes will cover the following columns:
+
+-- hwaddr and client_id
+-- For lease stability: if a client requests a new lease, try to find an
+-- existing or recently expired lease for it so that it can keep using the
+-- same IP address.
+
+-- Field Sizes
+-- ===========
+-- If any of the VARxxx field sizes are altered, the lengths in the PgSQL
+-- backend source file (pgsql_lease_mgr.cc) must be correspondingly changed.
+
+-- Portability
+-- ===========
+-- Some columns contain binary data so are stored as BYTEA instead of
+-- VARCHAR. This may be non-portable between databases: in this case, the
+-- definition should be changed to VARCHAR.
diff --git a/src/share/database/scripts/pgsql/dhcpdb_drop.pgsql b/src/share/database/scripts/pgsql/dhcpdb_drop.pgsql
new file mode 100644
index 0000000..1e88ab9
--- /dev/null
+++ b/src/share/database/scripts/pgsql/dhcpdb_drop.pgsql
@@ -0,0 +1,228 @@
+-- Copyright (C) 2016-2022 Internet Systems Consortium.
+
+-- This Source Code Form 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/.
+
+DROP TABLE IF EXISTS lease4 CASCADE;
+DROP TABLE IF EXISTS lease6 CASCADE;
+DROP TABLE IF EXISTS lease6_types CASCADE;
+DROP TABLE IF EXISTS schema_version CASCADE;
+DROP TABLE IF EXISTS lease_state CASCADE;
+DROP TABLE IF EXISTS dhcp4_options CASCADE;
+DROP TABLE IF EXISTS dhcp6_options CASCADE;
+DROP TABLE IF EXISTS ipv6_reservations CASCADE;
+DROP TABLE IF EXISTS lease_hwaddr_source CASCADE;
+DROP TABLE IF EXISTS host_identifier_type CASCADE;
+DROP TABLE IF EXISTS dhcp_option_scope CASCADE;
+DROP TABLE IF EXISTS hosts CASCADE;
+DROP FUNCTION IF EXISTS lease4DumpHeader();
+DROP FUNCTION IF EXISTS lease4DumpData();
+DROP FUNCTION IF EXISTS lease6DumpHeader();
+DROP FUNCTION IF EXISTS lease6DumpData();
+DROP TABLE IF EXISTS lease4_stat CASCADE;
+DROP FUNCTION IF EXISTS proc_stat_lease4_insert ();
+DROP FUNCTION IF EXISTS proc_stat_lease4_update ();
+DROP FUNCTION IF EXISTS proc_stat_lease4_delete ();
+DROP TABLE IF EXISTS lease6_stat CASCADE;
+DROP FUNCTION IF EXISTS proc_stat_lease6_insert ();
+DROP FUNCTION IF EXISTS proc_stat_lease6_update ();
+DROP FUNCTION IF EXISTS proc_stat_lease6_delete ();
+DROP TABLE IF EXISTS logs CASCADE;
+
+-- config backend procedures for DHCPv6
+DROP FUNCTION IF EXISTS createAuditRevisionDHCP6(audit_ts TIMESTAMP WITH TIME ZONE, server_tag VARCHAR(64),
+ audit_log_message TEXT, cascade_transaction BOOLEAN);
+DROP FUNCTION IF EXISTS createAuditEntryDHCP6(object_type_val VARCHAR(256), object_id_val BIGINT,
+ modification_type_val VARCHAR(32));
+DROP FUNCTION IF EXISTS createOptionAuditDHCP6(modification_type VARCHAR, scope_id SMALLINT,
+ option_id INT, subnet_id BIGINT, host_id INT,
+ network_name VARCHAR, pool_id BIGINT, pd_pool_id BIGINT,
+ modification_ts TIMESTAMP WITH TIME ZONE);
+DROP FUNCTION IF EXISTS createOptionAuditDHCP6(modification_type VARCHAR, scope_id SMALLINT,
+ option_id INT, p_subnet_id BIGINT, host_id INT,
+ network_name VARCHAR, pool_id BIGINT, pd_pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE);
+DROP FUNCTION IF EXISTS setClientClass6Order(id BIGINT, follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128));
+DROP FUNCTION IF EXISTS checkDHCPv6ClientClassDependency(class_id BIGINT, dependency_id BIGINT);
+DROP FUNCTION IF EXISTS updateDHCPv6ClientClassKnownDependency(client_class_id BIGINT,
+ dependency_id BIGINT);
+DROP FUNCTION IF EXISTS checkDHCPv6ClientClassKnownDependencyChange();
+
+-- config backend tables for DHCPv6
+DROP TABLE IF EXISTS dhcp6_audit CASCADE;
+DROP TABLE IF EXISTS dhcp6_option_def_server CASCADE;
+DROP TABLE IF EXISTS dhcp6_option_def CASCADE;
+DROP TABLE IF EXISTS dhcp6_options_server CASCADE;
+DROP TABLE IF EXISTS dhcp6_pool CASCADE;
+DROP TABLE IF EXISTS dhcp6_pd_pool CASCADE;
+DROP TABLE IF EXISTS dhcp6_subnet_server CASCADE;
+DROP TABLE IF EXISTS dhcp6_subnet CASCADE;
+DROP TABLE IF EXISTS dhcp6_shared_network_server CASCADE;
+DROP TABLE IF EXISTS dhcp6_shared_network CASCADE;
+DROP TABLE IF EXISTS dhcp6_global_parameter_server CASCADE;
+DROP TABLE IF EXISTS dhcp6_global_parameter CASCADE;
+DROP TABLE IF EXISTS dhcp6_server CASCADE;
+DROP TABLE IF EXISTS dhcp6_audit_revision CASCADE;
+DROP TABLE IF EXISTS dhcp6_client_class_server CASCADE;
+DROP TABLE IF EXISTS dhcp6_client_class_dependency CASCADE;
+DROP TABLE IF EXISTS dhcp6_client_class_order CASCADE;
+DROP TABLE IF EXISTS dhcp6_client_class CASCADE;
+
+-- drop trigger functions for DHCPv6
+DROP FUNCTION IF EXISTS func_dhcp6_client_class_AINS();
+DROP FUNCTION IF EXISTS func_dhcp6_client_class_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp6_client_class_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp6_client_class_check_dependency_BINS();
+DROP FUNCTION IF EXISTS func_dhcp6_client_class_dependency_AINS();
+DROP FUNCTION IF EXISTS func_dhcp6_pool_BDEL();
+DROP FUNCTION IF EXISTS func_dhcp6_global_parameter_AINS();
+DROP FUNCTION IF EXISTS func_dhcp6_global_parameter_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp6_global_parameter_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp6_subnet_AINS();
+DROP FUNCTION IF EXISTS func_dhcp6_subnet_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp6_shared_network_AINS();
+DROP FUNCTION IF EXISTS func_dhcp6_shared_network_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp6_shared_network_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp6_option_def_AINS();
+DROP FUNCTION IF EXISTS func_dhcp6_option_def_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp6_option_def_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp6_options_AINS();
+DROP FUNCTION IF EXISTS func_dhcp6_options_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp6_options_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp6_server_AINS();
+DROP FUNCTION IF EXISTS func_dhcp6_server_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp6_server_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp6_shared_network_BDEL();
+DROP FUNCTION IF EXISTS func_dhcp6_subnet_BDEL();
+DROP FUNCTION IF EXISTS func_dhcp6_pd_pool_BDEL();
+
+-- config backend procedures for DHCPv4
+DROP FUNCTION IF EXISTS createAuditRevisionDHCP4(audit_ts TIMESTAMP WITH TIME ZONE, server_tag VARCHAR(64),
+ audit_log_message TEXT, cascade_transaction BOOLEAN);
+DROP FUNCTION IF EXISTS createAuditEntryDHCP4(object_type_val VARCHAR(256), object_id_val BIGINT,
+ modification_type_val VARCHAR(32));
+DROP FUNCTION IF EXISTS createOptionAuditDHCP4(modification_type VARCHAR, scope_id SMALLINT,
+ option_id INT, subnet_id BIGINT, host_id INT,
+ network_name VARCHAR, pool_id BIGINT,
+ modification_ts TIMESTAMP WITH TIME ZONE);
+DROP FUNCTION IF EXISTS setClientClass4Order(id BIGINT, follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128));
+DROP FUNCTION IF EXISTS checkDHCPv4ClientClassDependency(class_id BIGINT, dependency_id BIGINT);
+DROP FUNCTION IF EXISTS updateDHCPv4ClientClassKnownDependency(client_class_id BIGINT,
+ dependency_id BIGINT);
+DROP FUNCTION IF EXISTS checkDHCPv4ClientClassKnownDependencyChange();
+
+-- config backend tables for DHCPv4
+DROP TABLE IF EXISTS dhcp4_audit CASCADE;
+DROP TABLE IF EXISTS dhcp4_option_def_server CASCADE;
+DROP TABLE IF EXISTS dhcp4_option_def CASCADE;
+DROP TABLE IF EXISTS dhcp4_options_server CASCADE;
+DROP TABLE IF EXISTS dhcp4_pool CASCADE;
+DROP TABLE IF EXISTS dhcp4_pd_pool CASCADE;
+DROP TABLE IF EXISTS dhcp4_subnet_server CASCADE;
+DROP TABLE IF EXISTS dhcp4_subnet CASCADE;
+DROP TABLE IF EXISTS dhcp4_shared_network_server CASCADE;
+DROP TABLE IF EXISTS dhcp4_shared_network CASCADE;
+DROP TABLE IF EXISTS dhcp4_global_parameter_server CASCADE;
+DROP TABLE IF EXISTS dhcp4_global_parameter CASCADE;
+DROP TABLE IF EXISTS dhcp4_server CASCADE;
+DROP TABLE IF EXISTS dhcp4_audit_revision CASCADE;
+DROP TABLE IF EXISTS dhcp4_client_class_server CASCADE;
+DROP TABLE IF EXISTS dhcp4_client_class_dependency CASCADE;
+DROP TABLE IF EXISTS dhcp4_client_class_order CASCADE;
+DROP TABLE IF EXISTS dhcp4_client_class CASCADE;
+
+-- drop trigger functions for DHCPv4
+DROP FUNCTION IF EXISTS func_dhcp4_client_class_AINS();
+DROP FUNCTION IF EXISTS func_dhcp4_client_class_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp4_client_class_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp4_client_class_check_dependency_BINS();
+DROP FUNCTION IF EXISTS func_dhcp4_client_class_dependency_AINS();
+DROP FUNCTION IF EXISTS func_dhcp4_pool_BDEL();
+DROP FUNCTION IF EXISTS func_dhcp4_global_parameter_AINS();
+DROP FUNCTION IF EXISTS func_dhcp4_global_parameter_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp4_global_parameter_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp4_subnet_AINS();
+DROP FUNCTION IF EXISTS func_dhcp4_subnet_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp4_shared_network_AINS();
+DROP FUNCTION IF EXISTS func_dhcp4_shared_network_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp4_shared_network_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp4_option_def_AINS();
+DROP FUNCTION IF EXISTS func_dhcp4_option_def_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp4_option_def_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp4_options_AINS();
+DROP FUNCTION IF EXISTS func_dhcp4_options_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp4_options_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp4_server_AINS();
+DROP FUNCTION IF EXISTS func_dhcp4_server_AUPD();
+DROP FUNCTION IF EXISTS func_dhcp4_server_ADEL();
+DROP FUNCTION IF EXISTS func_dhcp4_shared_network_BDEL();
+DROP FUNCTION IF EXISTS func_dhcp4_subnet_BDEL();
+
+-- common tables for config backend
+DROP TABLE IF EXISTS modification CASCADE;
+DROP TABLE IF EXISTS parameter_data_type CASCADE;
+DROP TABLE IF EXISTS ddns_replace_client_name_types CASCADE;
+DROP FUNCTION IF EXISTS modification_ts_update();
+DROP FUNCTION IF EXISTS get_session_boolean(name text);
+DROP FUNCTION IF EXISTS get_session_big_int(name text);
+DROP FUNCTION IF EXISTS get_session_value(name text);
+DROP FUNCTION IF EXISTS set_session_value(name text, value TEXT);
+DROP FUNCTION IF EXISTS set_session_value(name text, value BIGINT);
+DROP FUNCTION IF EXISTS set_session_value(name text, value BOOLEAN);
+
+DROP FUNCTION IF EXISTS lease4Upload(address VARCHAR, hwaddr VARCHAR,
+ client_id VARCHAR, valid_lifetime BIGINT, expire BIGINT, subnet_id BIGINT,
+ fqdn_fwd INT, fqdn_rev INT, hostname VARCHAR, state INT8,
+ user_context VARCHAR);
+DROP FUNCTION IF EXISTS lease6Upload(address VARCHAR, duid VARCHAR,
+ valid_lifetime BIGINT, expire BIGINT, subnet_id BIGINT,
+ pref_lifetime BIGINT, lease_type INT, iaid INT, prefix_len INT,
+ fqdn_fwd INT, fqdn_rev INT, hostname VARCHAR, hwaddr VARCHAR,
+ state INT8, user_context VARCHAR, hwtype INT, hwaddr_source INT);
+DROP FUNCTION IF EXISTS colonSeparatedHex(TEXT);
+
+DROP FUNCTION IF EXISTS gmt_epoch(input_time TIMESTAMP WITH TIME ZONE);
+
+-- lease limiting tables and functions
+DROP TABLE IF EXISTS lease4_stat_by_client_class;
+DROP TABLE IF EXISTS lease6_stat_by_client_class;
+DROP FUNCTION IF EXISTS lease4_AINS_lease4_stat(new_state BIGINT,
+ new_subnet_id BIGINT);
+DROP FUNCTION IF EXISTS lease4_AUPD_lease4_stat(old_state BIGINT,
+ old_subnet_id BIGINT, new_state BIGINT, new_subnet_id BIGINT);
+DROP FUNCTION IF EXISTS lease4_ADEL_lease4_stat(old_state BIGINT,
+ old_subnet_id BIGINT);
+DROP FUNCTION IF EXISTS lease6_AINS_lease6_stat(new_state BIGINT,
+ new_subnet_id BIGINT, new_lease_type SMALLINT);
+DROP FUNCTION IF EXISTS lease6_AUPD_lease6_stat(old_state BIGINT,
+ old_subnet_id BIGINT, old_lease_type SMALLINT, new_state BIGINT,
+ new_subnet_id BIGINT, new_lease_type SMALLINT);
+DROP FUNCTION IF EXISTS lease6_ADEL_lease6_stat(old_state BIGINT,
+ old_subnet_id BIGINT, old_lease_type SMALLINT);
+DROP FUNCTION IF EXISTS lease4_AINS_lease4_stat_by_client_class(
+ new_state BIGINT, new_user_context TEXT);
+DROP FUNCTION IF EXISTS lease4_AUPD_lease4_stat_by_client_class(
+ old_state BIGINT, old_user_context TEXT,
+ new_state BIGINT, new_user_context TEXT);
+DROP FUNCTION IF EXISTS lease4_ADEL_lease4_stat_by_client_class(
+ old_state BIGINT, old_user_context TEXT);
+DROP FUNCTION IF EXISTS lease6_AINS_lease6_stat_by_client_class(
+ new_state BIGINT, new_user_context TEXT, new_lease_type SMALLINT);
+DROP FUNCTION IF EXISTS lease6_AUPD_lease6_stat_by_client_class(
+ old_state BIGINT, old_user_context TEXT, old_lease_type SMALLINT,
+ new_state BIGINT, new_user_context TEXT, new_lease_type SMALLINT);
+DROP FUNCTION IF EXISTS lease6_ADEL_lease6_stat_by_client_class(
+ old_state BIGINT, old_user_context TEXT, old_lease_type SMALLINT);
+DROP FUNCTION IF EXISTS func_lease4_AINS();
+DROP FUNCTION IF EXISTS func_lease4_AUPD();
+DROP FUNCTION IF EXISTS func_lease4_ADEL();
+DROP FUNCTION IF EXISTS func_lease6_AINS();
+DROP FUNCTION IF EXISTS func_lease6_AUPD();
+DROP FUNCTION IF EXISTS func_lease6_ADEL();
+DROP FUNCTION IF EXISTS checkLease4Limits(user_context TEXT);
+DROP FUNCTION IF EXISTS checkLease6Limits(user_context TEXT);
+DROP FUNCTION IF EXISTS isJsonSupported();
+DROP FUNCTION IF EXISTS json_cast(json_candidate TEXT);
diff --git a/src/share/database/scripts/pgsql/upgrade_001.0_to_002.0.sh.in b/src/share/database/scripts/pgsql/upgrade_001.0_to_002.0.sh.in
new file mode 100644
index 0000000..658c92e
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_001.0_to_002.0.sh.in
@@ -0,0 +1,173 @@
+#!/bin/sh
+
+# Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "1.0" ]; then
+ printf 'This script upgrades 1.0 to 2.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+-- Add state column to the lease4 table.
+ALTER TABLE lease4
+ ADD COLUMN state INT8 DEFAULT 0;
+
+-- Add state column to the lease6 table.
+ALTER TABLE lease6
+ ADD COLUMN state INT8 DEFAULT 0;
+
+-- Create indexes for querying leases in a given state and segregated
+-- by the expiration time. One of the applications is to retrieve all
+-- expired leases. However, these indexes can be also used to retrieve
+-- leases in a given state regardless of the expiration time.
+CREATE INDEX lease4_by_state_expire ON lease4 (state ASC, expire ASC);
+CREATE INDEX lease6_by_state_expire ON lease6 (state ASC, expire ASC);
+
+-- Create table holding mapping of the lease states to their names.
+-- This is not used in queries from the DHCP server but rather in
+-- direct queries from the lease database management tools.
+CREATE TABLE lease_state (
+ state INT8 PRIMARY KEY NOT NULL,
+ name VARCHAR(64) NOT NULL);
+
+-- Insert currently defined state names.
+START TRANSACTION;
+INSERT INTO lease_state VALUES (0, 'default');
+INSERT INTO lease_state VALUES (1, 'declined');
+INSERT INTO lease_state VALUES (2, 'expired-reclaimed');
+COMMIT;
+
+-- Add a constraint that any state value added to the lease4 must
+-- map to a value in the lease_state table.
+ALTER TABLE lease4
+ ADD CONSTRAINT fk_lease4_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+-- Add a constraint that any state value added to the lease6 must
+-- map to a value in the lease_state table.
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_state FOREIGN KEY (state)
+ REFERENCES lease_state (state);
+
+-- Add a constraint that lease type in the lease6 table must map
+-- to a lease type defined in the lease6_types table.
+ALTER TABLE lease6
+ ADD CONSTRAINT fk_lease6_type FOREIGN KEY (lease_type)
+ REFERENCES lease6_types (lease_type);
+
+--
+-- FUNCTION that returns a result set containing the column names for lease4 dumps
+DROP FUNCTION IF EXISTS lease4DumpHeader();
+CREATE FUNCTION lease4DumpHeader() RETURNS text AS \$\$
+ select cast('address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state' as text) as result;
+\$\$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the data for lease4 dumps
+DROP FUNCTION IF EXISTS lease4DumpData();
+CREATE FUNCTION lease4DumpData() RETURNS
+ table (address inet,
+ hwaddr text,
+ client_id text,
+ valid_lifetime bigint,
+ expire timestamp with time zone,
+ subnet_id bigint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text
+ ) as \$\$
+ SELECT ('0.0.0.0'::inet + l.address),
+ encode(l.hwaddr,'hex'),
+ encode(l.client_id,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name
+ FROM lease4 l
+ left outer join lease_state s on (l.state = s.state);
+\$\$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the column names for lease6 dumps
+DROP FUNCTION IF EXISTS lease6DumpHeader();
+CREATE FUNCTION lease6DumpHeader() RETURNS text AS \$\$
+ select cast('address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,state' as text) as result;
+\$\$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the data for lease6 dumps
+DROP FUNCTION IF EXISTS lease6DumpData();
+CREATE FUNCTION lease6DumpData() RETURNS
+ TABLE (
+ address text,
+ duid text,
+ valid_lifetime bigint,
+ expire timestamp with time zone,
+ subnet_id bigint,
+ pref_lifetime bigint,
+ name text,
+ iaid integer,
+ prefix_len smallint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text
+ ) AS \$\$
+ SELECT (l.address,
+ encode(l.duid,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ t.name,
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name)
+ FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state);
+\$\$ LANGUAGE SQL;
+--
+
+-- Set 2.0 schema version.
+START TRANSACTION;
+UPDATE schema_version
+ SET version = '2', minor = '0';
+COMMIT;
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_002.0_to_003.0.sh.in b/src/share/database/scripts/pgsql/upgrade_002.0_to_003.0.sh.in
new file mode 100644
index 0000000..bf79353
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_002.0_to_003.0.sh.in
@@ -0,0 +1,293 @@
+#!/bin/sh
+
+# Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "2.0" ]; then
+ printf 'This script upgrades 2.0 to 3.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- Upgrade to schema 3.0 begins here:
+
+--
+-- Table structure for table host_identifier_type
+--
+
+CREATE TABLE host_identifier_type (
+ type SMALLINT PRIMARY KEY NOT NULL,
+ name VARCHAR(32) DEFAULT NULL
+);
+
+INSERT INTO host_identifier_type VALUES (0, 'hw-address');
+INSERT INTO host_identifier_type VALUES (1, 'duid');
+INSERT INTO host_identifier_type VALUES (2, 'circuit-id');
+
+CREATE TABLE dhcp_option_scope (
+ scope_id SMALLINT PRIMARY KEY NOT NULL,
+ scope_name varchar(32) DEFAULT NULL
+);
+
+INSERT INTO dhcp_option_scope VALUES (0, 'global');
+INSERT INTO dhcp_option_scope VALUES (1, 'subnet');
+INSERT INTO dhcp_option_scope VALUES (2, 'client-class');
+INSERT INTO dhcp_option_scope VALUES (3, 'host');
+
+--
+-- Table structure for table hosts
+--
+-- Primary key and unique constraints automatically create indexes
+-- foreign key constraints do not
+CREATE TABLE hosts (
+ host_id SERIAL PRIMARY KEY NOT NULL,
+ dhcp_identifier BYTEA NOT NULL,
+ dhcp_identifier_type SMALLINT NOT NULL,
+ dhcp4_subnet_id INT DEFAULT NULL,
+ dhcp6_subnet_id INT DEFAULT NULL,
+ ipv4_address BIGINT DEFAULT NULL,
+ hostname VARCHAR(255) DEFAULT NULL,
+ dhcp4_client_classes VARCHAR(255) DEFAULT NULL,
+ dhcp6_client_classes VARCHAR(255) DEFAULT NULL,
+ CONSTRAINT key_dhcp4_ipv4_address_subnet_id UNIQUE (ipv4_address, dhcp4_subnet_id),
+ CONSTRAINT key_dhcp4_identifier_subnet_id UNIQUE (dhcp_identifier, dhcp_identifier_type, dhcp4_subnet_id),
+ CONSTRAINT key_dhcp6_identifier_subnet_id UNIQUE (dhcp_identifier, dhcp_identifier_type, dhcp6_subnet_id),
+ CONSTRAINT fk_host_identifier_type FOREIGN KEY (dhcp_identifier_type) REFERENCES host_identifier_type (type)
+ ON DELETE CASCADE
+);
+
+CREATE INDEX fk_host_identifier_type ON hosts (dhcp_identifier_type);
+
+--
+-- Table structure for table dhcp4_options
+--
+
+CREATE TABLE dhcp4_options (
+ option_id SERIAL PRIMARY KEY NOT NULL,
+ code SMALLINT NOT NULL,
+ value BYTEA,
+ formatted_value TEXT,
+ space VARCHAR(128) DEFAULT NULL,
+ persistent BOOLEAN NOT NULL DEFAULT 'f',
+ dhcp_client_class VARCHAR(128) DEFAULT NULL,
+ dhcp4_subnet_id INT DEFAULT NULL,
+ host_id INT DEFAULT NULL,
+ scope_id SMALLINT NOT NULL,
+ CONSTRAINT fk_options_host1 FOREIGN KEY (host_id) REFERENCES hosts (host_id) ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp4_option_scode FOREIGN KEY (scope_id) REFERENCES dhcp_option_scope (scope_id) ON DELETE CASCADE
+);
+
+CREATE INDEX fk_dhcp4_options_host1_idx ON dhcp4_options (host_id);
+CREATE INDEX fk_dhcp4_options_scope_idx ON dhcp4_options (scope_id);
+
+--
+-- Table structure for table dhcp6_options
+--
+
+CREATE TABLE dhcp6_options (
+ option_id SERIAL PRIMARY KEY NOT NULL,
+ code INT NOT NULL,
+ value BYTEA,
+ formatted_value TEXT,
+ space VARCHAR(128) DEFAULT NULL,
+ persistent BOOLEAN NOT NULL DEFAULT 'f',
+ dhcp_client_class VARCHAR(128) DEFAULT NULL,
+ dhcp6_subnet_id INT DEFAULT NULL,
+ host_id INT DEFAULT NULL,
+ scope_id SMALLINT NOT NULL,
+ CONSTRAINT fk_options_host10 FOREIGN KEY (host_id) REFERENCES hosts (host_id) ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp6_option_scode FOREIGN KEY (scope_id) REFERENCES dhcp_option_scope (scope_id) ON DELETE CASCADE
+);
+
+CREATE INDEX fk_dhcp6_options_host1_idx ON dhcp6_options (host_id);
+CREATE INDEX fk_dhcp6_options_scope_idx ON dhcp6_options (scope_id);
+
+--
+-- Table structure for table ipv6_reservations
+--
+
+CREATE TABLE ipv6_reservations (
+ reservation_id SERIAL PRIMARY KEY NOT NULL,
+ address VARCHAR(39) NOT NULL,
+ prefix_len SMALLINT NOT NULL DEFAULT '128',
+ type SMALLINT NOT NULL DEFAULT '0',
+ dhcp6_iaid INT DEFAULT NULL,
+ host_id INT NOT NULL,
+ CONSTRAINT key_dhcp6_address_prefix_len UNIQUE (address, prefix_len),
+ CONSTRAINT fk_ipv6_reservations_host FOREIGN KEY (host_id) REFERENCES hosts (host_id) ON DELETE CASCADE
+);
+
+CREATE INDEX fk_ipv6_reservations_host_idx ON ipv6_reservations (host_id);
+
+--
+-- Table structure for table lease_hwaddr_source
+--
+
+CREATE TABLE lease_hwaddr_source (
+ hwaddr_source INT PRIMARY KEY NOT NULL,
+ name VARCHAR(40) DEFAULT NULL
+);
+
+-- In the event hardware address cannot be determined, we need to satisfy
+-- foreign key constraint between lease6 and lease_hardware_source
+INSERT INTO lease_hwaddr_source VALUES (0, 'HWADDR_SOURCE_UNKNOWN');
+
+-- Hardware address obtained from raw sockets
+INSERT INTO lease_hwaddr_source VALUES (1, 'HWADDR_SOURCE_RAW');
+
+-- Hardware address converted from IPv6 link-local address with EUI-64
+INSERT INTO lease_hwaddr_source VALUES (2, 'HWADDR_SOURCE_IPV6_LINK_LOCAL');
+
+-- Hardware address extracted from client-id (duid)
+INSERT INTO lease_hwaddr_source VALUES (4, 'HWADDR_SOURCE_DUID');
+
+-- Hardware address extracted from client address relay option (RFC6939)
+INSERT INTO lease_hwaddr_source VALUES (8, 'HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION');
+
+-- Hardware address extracted from remote-id option (RFC4649)
+INSERT INTO lease_hwaddr_source VALUES (16, 'HWADDR_SOURCE_REMOTE_ID');
+
+-- Hardware address extracted from subscriber-id option (RFC4580)
+INSERT INTO lease_hwaddr_source VALUES (32, 'HWADDR_SOURCE_SUBSCRIBER_ID');
+
+-- Hardware address extracted from docsis options
+INSERT INTO lease_hwaddr_source VALUES (64, 'HWADDR_SOURCE_DOCSIS_CMTS');
+
+INSERT INTO lease_hwaddr_source VALUES (128, 'HWADDR_SOURCE_DOCSIS_MODEM');
+
+-- Adding ORDER BY clause to sort by lease address
+--
+-- FUNCTION that returns a result set containing the data for lease4 dumps
+DROP FUNCTION IF EXISTS lease4DumpData();
+CREATE FUNCTION lease4DumpData() RETURNS
+ table (address inet,
+ hwaddr text,
+ client_id text,
+ valid_lifetime bigint,
+ expire timestamp with time zone,
+ subnet_id bigint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text
+ ) as \$\$
+ SELECT ('0.0.0.0'::inet + l.address),
+ encode(l.hwaddr,'hex'),
+ encode(l.client_id,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name
+ FROM lease4 l
+ left outer join lease_state s on (l.state = s.state)
+ ORDER BY l.address;
+\$\$ LANGUAGE SQL;
+--
+
+-- Add new columns to lease6
+ALTER TABLE lease6
+ ADD COLUMN hwaddr BYTEA DEFAULT NULL,
+ ADD COLUMN hwtype SMALLINT DEFAULT NULL,
+ ADD COLUMN hwaddr_source SMALLINT DEFAULT NULL;
+
+--
+-- FUNCTION that returns a result set containing the column names for lease6 dumps
+DROP FUNCTION IF EXISTS lease6DumpHeader();
+CREATE FUNCTION lease6DumpHeader() RETURNS text AS \$\$
+ select cast('address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,state,hwaddr,hwtype,hwaddr_source' as text) as result;
+\$\$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the data for lease6 dumps
+DROP FUNCTION IF EXISTS lease6DumpData();
+CREATE FUNCTION lease6DumpData() RETURNS
+ TABLE (
+ address text,
+ duid text,
+ valid_lifetime bigint,
+ expire timestamp with time zone,
+ subnet_id bigint,
+ pref_lifetime bigint,
+ name text,
+ iaid integer,
+ prefix_len smallint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text,
+ hwaddr text,
+ hwtype smallint,
+ hwaddr_source text
+ ) AS \$\$
+ SELECT (l.address,
+ encode(l.duid,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ t.name,
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name,
+ encode(l.hwaddr,'hex'),
+ l.hwtype,
+ h.name
+ )
+ FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state)
+ left outer join lease_hwaddr_source h on (l.hwaddr_source = h.hwaddr_source)
+ ORDER BY l.address;
+\$\$ LANGUAGE SQL;
+
+-- Add columns holding reservations for siaddr, sname and file fields
+-- carried within DHCPv4 message.
+ALTER TABLE hosts ADD COLUMN dhcp4_next_server BIGINT DEFAULT NULL;
+ALTER TABLE hosts ADD COLUMN dhcp4_server_hostname VARCHAR(64) DEFAULT NULL;
+ALTER TABLE hosts ADD COLUMN dhcp4_boot_file_name VARCHAR(128) DEFAULT NULL;
+
+-- Set 3.0 schema version.
+UPDATE schema_version
+ SET version = '3', minor = '0';
+
+-- Schema 3.0 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_003.0_to_003.1.sh.in b/src/share/database/scripts/pgsql/upgrade_003.0_to_003.1.sh.in
new file mode 100644
index 0000000..d5013cd
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_003.0_to_003.1.sh.in
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+# Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "3.0" ]; then
+ printf 'This script upgrades 3.0 to 3.1. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- Upgrade to schema 3.1 begins here:
+
+-- The 'client-id' host identifier type was missing in the
+-- 2.0 -> 3.0 upgrade script. However, it was present in the
+-- dhcpdb_create.pgsql file. This means that this entry may
+-- or may not be present. By the conditional insert below we
+-- will only insert it if it doesn't exist.
+INSERT INTO host_identifier_type (type, name)
+ SELECT 3, 'client-id'
+ WHERE NOT EXISTS (
+ SELECT type FROM host_identifier_type WHERE type = 3
+ );
+
+-- We also add a new identifier type: flex-id.
+INSERT INTO host_identifier_type VALUES (4, 'flex-id');
+
+-- Set 3.1 schema version.
+UPDATE schema_version
+ SET version = '3', minor = '1';
+
+-- Schema 3.1 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_003.1_to_003.2.sh.in b/src/share/database/scripts/pgsql/upgrade_003.1_to_003.2.sh.in
new file mode 100644
index 0000000..f9ec2bb
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_003.1_to_003.2.sh.in
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+# Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "3.1" ]; then
+ printf 'This script upgrades 3.1 to 3.2. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- Remove constraints which perform too restrictive checks on the inserted
+-- host reservations. We want to be able to insert host reservations which
+-- include no specific IPv4 address or those that have repeating subnet
+-- identifiers, e.g. IPv4 reservations would typically include 0 (or null)
+-- IPv6 subnet identifiers.
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp4_ipv4_address_subnet_id;
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp4_identifier_subnet_id;
+ALTER TABLE hosts DROP CONSTRAINT key_dhcp6_identifier_subnet_id;
+
+-- Create partial indexes instead of the constraints that we have removed.
+
+-- IPv4 address/IPv4 subnet identifier pair is unique if subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX hosts_dhcp4_ipv4_address_subnet_id ON hosts
+ (ipv4_address ASC, dhcp4_subnet_id ASC)
+ WHERE ipv4_address IS NOT NULL AND ipv4_address <> 0;
+
+-- Client identifier is unique within an IPv4 subnet when subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX hosts_dhcp4_identifier_subnet_id ON hosts
+ (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp4_subnet_id ASC)
+ WHERE (dhcp4_subnet_id IS NOT NULL AND dhcp4_subnet_id <> 0);
+
+-- Client identifier is unique within an IPv6 subnet when subnet identifier is
+-- not null and not 0.
+CREATE UNIQUE INDEX hosts_dhcp6_identifier_subnet_id ON hosts
+ (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp6_subnet_id ASC)
+ WHERE (dhcp6_subnet_id IS NOT NULL AND dhcp6_subnet_id <> 0);
+
+-- Set 3.2 schema version.
+UPDATE schema_version
+ SET version = '3', minor = '2';
+
+-- Schema 3.2 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_003.2_to_003.3.sh.in b/src/share/database/scripts/pgsql/upgrade_003.2_to_003.3.sh.in
new file mode 100644
index 0000000..6933c16
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_003.2_to_003.3.sh.in
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "3.2" ]; then
+ printf 'This script upgrades 3.2 to 3.3. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- Change subnet ID columns type to BIGINT to match lease4/6 tables
+ALTER TABLE hosts ALTER COLUMN dhcp4_subnet_id TYPE BIGINT;
+ALTER TABLE hosts ALTER COLUMN dhcp6_subnet_id TYPE BIGINT;
+
+ALTER TABLE dhcp4_options ALTER COLUMN dhcp4_subnet_id TYPE BIGINT;
+ALTER TABLE dhcp6_options ALTER COLUMN dhcp6_subnet_id TYPE BIGINT;
+
+-- Set 3.3 schema version.
+UPDATE schema_version
+ SET version = '3', minor = '3';
+
+-- Schema 3.3 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_003.3_to_004.0.sh.in b/src/share/database/scripts/pgsql/upgrade_003.3_to_004.0.sh.in
new file mode 100644
index 0000000..d7a0885
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_003.3_to_004.0.sh.in
@@ -0,0 +1,261 @@
+#!/bin/sh
+
+# Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "3.3" ]; then
+ printf 'This script upgrades 3.3 to 4.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- Add a column holding hosts for user context.
+ALTER TABLE hosts ADD COLUMN user_context TEXT;
+
+-- Add a column holding DHCP options for user context.
+ALTER TABLE dhcp4_options ADD COLUMN user_context TEXT;
+ALTER TABLE dhcp6_options ADD COLUMN user_context TEXT;
+
+-- Create index for searching leases by subnet identifier.
+CREATE INDEX lease4_by_subnet_id ON lease4 (subnet_id);
+
+-- Create for searching leases by subnet identifier and lease type.
+CREATE INDEX lease6_by_subnet_id_lease_type ON lease6 (subnet_id, lease_type);
+
+-- The index by iaid_subnet_id_duid is not the best choice because there are
+-- cases when we don't specify subnet identifier while searching leases. The
+-- index will be universal if the subnet_id is the right most column in the
+-- index.
+DROP INDEX lease6_by_iaid_subnet_id_duid;
+CREATE INDEX lease6_by_duid_iaid_subnet_id ON lease6 (duid, iaid, subnet_id);
+
+-- Create v4 lease statistics table
+CREATE TABLE lease4_stat (
+ subnet_id BIGINT NOT NULL,
+ state INT8 NOT NULL,
+ leases BIGINT,
+ PRIMARY KEY (subnet_id, state)
+);
+
+--
+-- Create v4 insert trigger procedure
+CREATE FUNCTION proc_stat_lease4_insert () RETURNS trigger AS \$stat_lease4_insert\$
+BEGIN
+ IF NEW.state < 2 THEN
+ UPDATE lease4_stat
+ SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat VALUES (new.subnet_id, new.state, 1);
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+\$stat_lease4_insert\$ LANGUAGE plpgsql;
+
+-- Create v4 insert trigger procedure
+CREATE TRIGGER stat_lease4_insert
+AFTER INSERT ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease4_insert();
+
+--
+-- Create v4 update trigger procedure
+CREATE FUNCTION proc_stat_lease4_update () RETURNS trigger AS \$stat_lease4_update\$
+BEGIN
+ IF OLD.state != NEW.state THEN
+ IF OLD.state < 2 THEN
+ -- Decrement the old state count if record exists
+ UPDATE lease4_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND state = OLD.state;
+ END IF;
+
+ IF NEW.state < 2 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ -- Insert new state record if it does not exist
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat VALUES (NEW.subnet_id, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+\$stat_lease4_update\$ LANGUAGE plpgsql;
+
+-- Create v4 update trigger
+CREATE TRIGGER stat_lease4_update
+AFTER UPDATE ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease4_update();
+
+--
+-- Create the v4 delete trigger procedure
+CREATE FUNCTION proc_stat_lease4_delete () RETURNS trigger AS \$stat_lease4_delete\$
+BEGIN
+ IF OLD.state < 2 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease4_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND OLD.state = state;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+\$stat_lease4_delete\$ LANGUAGE plpgsql;
+
+-- Create the v4 delete trigger
+CREATE TRIGGER stat_lease4_delete
+AFTER DELETE ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease4_delete();
+
+-- Create v6 lease statistics table
+CREATE TABLE lease6_stat (
+ subnet_id BIGINT NOT NULL,
+ lease_type SMALLINT NOT NULL,
+ state INT8 NOT NULL,
+ leases BIGINT,
+ PRIMARY KEY (subnet_id, lease_type, state)
+);
+
+--
+-- Create v6 insert trigger procedure
+CREATE FUNCTION proc_stat_lease6_insert () RETURNS trigger AS \$stat_lease6_insert\$
+BEGIN
+ IF NEW.state < 2 THEN
+ UPDATE lease6_stat
+ SET leases = leases + 1
+ WHERE
+ subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat
+ VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+\$stat_lease6_insert\$ LANGUAGE plpgsql;
+
+-- Create v6 insert trigger procedure
+CREATE TRIGGER stat_lease6_insert
+AFTER INSERT ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease6_insert();
+
+--
+-- Create v6 update trigger procedure
+CREATE FUNCTION proc_stat_lease6_update () RETURNS trigger AS \$stat_lease6_update\$
+BEGIN
+ IF OLD.state != NEW.state THEN
+ IF OLD.state < 2 THEN
+ -- Decrement the old state count if record exists
+ UPDATE lease6_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+
+ IF NEW.state < 2 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ -- Insert new state record if it does not exist
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+\$stat_lease6_update\$ LANGUAGE plpgsql;
+
+-- Create v6 update trigger
+CREATE TRIGGER stat_lease6_update
+AFTER UPDATE ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease6_update();
+
+--
+-- Create the v6 delete trigger procedure
+CREATE FUNCTION proc_stat_lease6_delete() RETURNS trigger AS \$stat_lease6_delete\$
+BEGIN
+ IF OLD.state < 2 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease6_stat SET leases = leases - 1
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND OLD.state = state;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+\$stat_lease6_delete\$ LANGUAGE plpgsql;
+
+-- Create the v6 delete trigger
+CREATE TRIGGER stat_lease6_delete
+AFTER DELETE ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE proc_stat_lease6_delete();
+
+-- Populate lease4_stat table based on existing leases
+-- We only care about assigned and declined states
+INSERT INTO lease4_stat (subnet_id, state, leases)
+ SELECT subnet_id, state, count(state)
+ FROM lease4 WHERE state < 2
+ GROUP BY subnet_id, state ORDER BY subnet_id;
+
+-- Populate lease6_stat table based on existing leases
+-- We only care about assigned and declined states
+INSERT INTO lease6_stat (subnet_id, lease_type, state, leases)
+ SELECT subnet_id, lease_type, state, count(state)
+ FROM lease6 WHERE state < 2
+ GROUP BY subnet_id, lease_type, state
+ ORDER BY subnet_id;
+
+-- Set 4.0 schema version.
+UPDATE schema_version
+ SET version = '4', minor = '0';
+
+-- Schema 4.0 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_004.0_to_005.0.sh.in b/src/share/database/scripts/pgsql/upgrade_004.0_to_005.0.sh.in
new file mode 100644
index 0000000..62c861d
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_004.0_to_005.0.sh.in
@@ -0,0 +1,172 @@
+#!/bin/sh
+
+# Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "4.0" ]; then
+ printf 'This script upgrades 4.0 to 5.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- Add a column holding leases for user context.
+ALTER TABLE lease4 ADD COLUMN user_context TEXT;
+ALTER TABLE lease6 ADD COLUMN user_context TEXT;
+
+--
+-- FUNCTION that returns a result set containing the column names for lease4 dumps
+DROP FUNCTION IF EXISTS lease4DumpHeader();
+CREATE FUNCTION lease4DumpHeader() RETURNS text AS \$\$
+ select cast('address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context' as text) as result;
+\$\$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the data for lease4 dumps
+DROP FUNCTION IF EXISTS lease4DumpData();
+CREATE FUNCTION lease4DumpData() RETURNS
+ table (address inet,
+ hwaddr text,
+ client_id text,
+ valid_lifetime bigint,
+ expire timestamp with time zone,
+ subnet_id bigint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text,
+ user_context text
+ ) as \$\$
+ SELECT ('0.0.0.0'::inet + l.address),
+ encode(l.hwaddr,'hex'),
+ encode(l.client_id,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name,
+ l.user_context
+ FROM lease4 l
+ left outer join lease_state s on (l.state = s.state)
+ ORDER BY l.address;
+\$\$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the column names for lease6 dumps
+DROP FUNCTION IF EXISTS lease6DumpHeader();
+CREATE FUNCTION lease6DumpHeader() RETURNS text AS \$\$
+ select cast('address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,state,hwaddr,hwtype,hwaddr_source,user_context' as text) as result;
+\$\$ LANGUAGE SQL;
+--
+
+--
+-- FUNCTION that returns a result set containing the data for lease6 dumps
+DROP FUNCTION IF EXISTS lease6DumpData();
+CREATE FUNCTION lease6DumpData() RETURNS
+ TABLE (
+ address text,
+ duid text,
+ valid_lifetime bigint,
+ expire timestamp with time zone,
+ subnet_id bigint,
+ pref_lifetime bigint,
+ name text,
+ iaid integer,
+ prefix_len smallint,
+ fqdn_fwd int,
+ fqdn_rev int,
+ hostname text,
+ state text,
+ hwaddr text,
+ hwtype smallint,
+ hwaddr_source text,
+ user_context text
+ ) AS \$\$
+ SELECT (l.address,
+ encode(l.duid,'hex'),
+ l.valid_lifetime,
+ l.expire,
+ l.subnet_id,
+ l.pref_lifetime,
+ t.name,
+ l.iaid,
+ l.prefix_len,
+ l.fqdn_fwd::int,
+ l.fqdn_rev::int,
+ l.hostname,
+ s.name,
+ encode(l.hwaddr,'hex'),
+ l.hwtype,
+ h.name,
+ l.user_context
+ )
+ FROM lease6 l
+ left outer join lease6_types t on (l.lease_type = t.lease_type)
+ left outer join lease_state s on (l.state = s.state)
+ left outer join lease_hwaddr_source h on (l.hwaddr_source = h.hwaddr_source)
+ ORDER BY l.address;
+\$\$ LANGUAGE SQL;
+--
+
+-- Create logs table (logs table is used by forensic logging hook library)
+CREATE TABLE logs (
+ timestamp TIMESTAMP WITH TIME ZONE
+ DEFAULT CURRENT_TIMESTAMP, -- creation timestamp
+ address VARCHAR(43) NULL, -- address or prefix
+ log TEXT NOT NULL -- the log itself
+ );
+
+-- Create search indexes
+CREATE INDEX timestamp_id ON logs (timestamp);
+CREATE INDEX address_id ON logs (address);
+
+-- Create auth_key in hosts table for storing keys for DHCPv6 reconfigure.
+ALTER TABLE hosts ADD COLUMN auth_key VARCHAR(16) DEFAULT NULL;
+
+-- Convert subnet-id values of 0 to NULL
+UPDATE hosts SET dhcp4_subnet_id = NULL WHERE dhcp4_subnet_id = 0;
+UPDATE dhcp4_options SET dhcp4_subnet_id = NULL WHERE dhcp4_subnet_id = 0;
+UPDATE hosts SET dhcp6_subnet_id = NULL WHERE dhcp6_subnet_id = 0;
+UPDATE dhcp6_options SET dhcp6_subnet_id = NULL WHERE dhcp6_subnet_id = 0;
+
+-- Set 5.0 schema version.
+UPDATE schema_version
+ SET version = '5', minor = '0';
+
+-- Schema 5.0 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_005.0_to_005.1.sh.in b/src/share/database/scripts/pgsql/upgrade_005.0_to_005.1.sh.in
new file mode 100644
index 0000000..bd3f5d1
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_005.0_to_005.1.sh.in
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+# Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "5.0" ]; then
+ printf 'This script upgrades 5.0 to 5.1. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- Put the auth key in hexadecimal (double size but far more user friendly).
+ALTER TABLE hosts ALTER COLUMN auth_key TYPE VARCHAR(32);
+
+-- Set 5.1 schema version.
+UPDATE schema_version
+ SET version = '5', minor = '1';
+
+-- Schema 5.1a specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_005.1_to_006.0.sh.in b/src/share/database/scripts/pgsql/upgrade_005.1_to_006.0.sh.in
new file mode 100644
index 0000000..b1bf2d3
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_005.1_to_006.0.sh.in
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+# Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "5.1" ]; then
+ printf 'This script upgrades 5.1 to 6.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- Create a lower case hostname index for hosts.
+CREATE INDEX hosts_by_hostname ON hosts (lower(hostname))
+WHERE hostname IS NOT NULL;
+
+-- Create a hostname index for lease4.
+CREATE INDEX lease4_by_hostname ON lease4 (lower(hostname))
+WHERE hostname IS NOT NULL;
+
+-- Create a hostname index for lease6.
+CREATE INDEX lease6_by_hostname ON lease6 (lower(hostname))
+WHERE hostname IS NOT NULL;
+
+-- Move to lower case hostnames in lease4 table.
+-- Not required so in comment
+-- UPDATE lease4 SET hostname = lower(hostname)
+-- WHERE lower(hostname) != hostname;
+
+-- Move to lower case hostnames in lease6 table.
+-- Not required so in comment
+-- UPDATE lease6 SET hostname = lower(hostname)
+-- WHERE lower(hostname) != hostname;
+
+-- Set 6.0 schema version.
+UPDATE schema_version
+ SET version = '6', minor = '0';
+
+-- Schema 5.1a specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_006.0_to_006.1.sh.in b/src/share/database/scripts/pgsql/upgrade_006.0_to_006.1.sh.in
new file mode 100644
index 0000000..b036c52
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_006.0_to_006.1.sh.in
@@ -0,0 +1,139 @@
+#!/bin/sh
+
+# Copyright (C) 2020-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "6.0" ]; then
+ printf 'This script upgrades 6.0 to 6.1. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- Fix v4 update trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease4_update () RETURNS trigger AS \$stat_lease4_update\$
+BEGIN
+ IF OLD.subnet_id != NEW.subnet_id OR OLD.state != NEW.state THEN
+ IF OLD.state < 2 THEN
+ -- Decrement the old state count if record exists
+ UPDATE lease4_stat SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND state = OLD.state;
+ END IF;
+
+ IF NEW.state < 2 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND state = NEW.state;
+
+ -- Insert new state record if it does not exist
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat VALUES (NEW.subnet_id, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+\$stat_lease4_update\$ LANGUAGE plpgsql;
+
+--
+-- Fix the v4 delete trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease4_delete () RETURNS trigger AS \$stat_lease4_delete\$
+BEGIN
+ IF OLD.state < 2 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease4_stat SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND OLD.state = state;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+\$stat_lease4_delete\$ LANGUAGE plpgsql;
+
+--
+-- Fix v6 update trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease6_update () RETURNS trigger AS \$stat_lease6_update\$
+BEGIN
+ IF OLD.subnet_id != NEW.subnet_id OR
+ OLD.lease_type != NEW.lease_type OR
+ OLD.state != NEW.state THEN
+ IF OLD.state < 2 THEN
+ -- Decrement the old state count if record exists
+ UPDATE lease6_stat SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND state = OLD.state;
+ END IF;
+
+ IF NEW.state < 2 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = NEW.subnet_id AND lease_type = NEW.lease_type
+ AND state = NEW.state;
+
+ -- Insert new state record if it does not exist
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat VALUES (NEW.subnet_id, NEW.lease_type, NEW.state, 1);
+ END IF;
+ END IF;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+\$stat_lease6_update\$ LANGUAGE plpgsql;
+
+--
+-- Fix the v6 delete trigger procedure
+CREATE OR REPLACE FUNCTION proc_stat_lease6_delete() RETURNS trigger AS \$stat_lease6_delete\$
+BEGIN
+ IF OLD.state < 2 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease6_stat SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = OLD.subnet_id AND lease_type = OLD.lease_type
+ AND OLD.state = state;
+ END IF;
+
+ -- Return is ignored since this is an after insert
+ RETURN NULL;
+END;
+\$stat_lease6_delete\$ LANGUAGE plpgsql;
+
+-- Set 6.1 schema version.
+UPDATE schema_version
+ SET version = '6', minor = '1';
+
+-- Schema 6.1 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_006.1_to_006.2.sh.in b/src/share/database/scripts/pgsql/upgrade_006.1_to_006.2.sh.in
new file mode 100644
index 0000000..76bb8cd
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_006.1_to_006.2.sh.in
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+# Copyright (C) 2020-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "6.1" ]; then
+ printf 'This script upgrades 6.1 to 6.2. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+-- Starting from this version we allow specifying multiple IP reservations
+-- for the same address in certain DHCP configurations. The server may check
+-- uniqueness of the IP addresses on its own. This is no longer checked at
+-- the database level to facilitate the use cases when a single host may
+-- get the same reserved IP address via different interfaces.
+
+-- Replace the unique index with non-unique index so the queries for
+-- hosts by IPv4 address are still efficient.
+--
+-- Note we have introduced a bug a while ago which causes the index to have
+-- different names depending on whether the schema was created via the
+-- dhcpdb_create.pgsql script or via updates. Therefore, let's make sure
+-- that we drop the index regardless of its current name.
+DROP INDEX IF EXISTS key_dhcp4_ipv4_address_subnet_id;
+DROP INDEX IF EXISTS hosts_dhcp4_ipv4_address_subnet_id;
+CREATE INDEX key_dhcp4_ipv4_address_subnet_id
+ ON hosts (ipv4_address ASC, dhcp4_subnet_id ASC);
+
+-- Replace the unique index with non-unique index so the queries for
+-- hosts by IPv6 address are still efficient.
+ALTER TABLE ipv6_reservations DROP CONSTRAINT IF EXISTS key_dhcp6_address_prefix_len;
+CREATE INDEX key_dhcp6_address_prefix_len
+ ON ipv6_reservations (address ASC, prefix_len ASC);
+
+-- Update the schema version number
+UPDATE schema_version
+ SET version = '6', minor = '2';
+
+-- Schema 6.2 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_006.2_to_007.0.sh.in b/src/share/database/scripts/pgsql/upgrade_006.2_to_007.0.sh.in
new file mode 100644
index 0000000..0feeb0a
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_006.2_to_007.0.sh.in
@@ -0,0 +1,2807 @@
+#!/bin/sh
+
+# Copyright (C) 2021-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "6.2" ]; then
+ printf 'This script upgrades 6.2 to 7.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+
+START TRANSACTION;
+
+-- This starts schema update to 7.0. It adds a lot (20+) of tables for the config backend.
+
+
+-- Adding on update trigger in MySQL is as easy as using this column definition in CREATE TABLE:
+-- modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+-- Sadly, Postgres has its own convoluted way of doing this. Luckily, the update pattern is
+-- the same in many tables, so we can define the trigger function once and the use it everywhere.
+
+-- First, we need to define a function that will do the actual job.
+-- This is used in many, many tables.
+CREATE OR REPLACE FUNCTION modification_ts_update()
+ RETURNS trigger AS \$modification_ts_update\$
+ BEGIN
+ new.modification_ts = CURRENT_TIMESTAMP;
+ RETURN NULL;
+ END;
+
+-- Second, we need to specify which language it was written in.
+\$modification_ts_update\$ LANGUAGE plpgsql;
+
+
+-- Create table modification and insert values for modification types.
+CREATE TABLE modification (
+ id smallint NOT NULL,
+ modification_type VARCHAR(32) NOT NULL,
+ PRIMARY KEY (id)
+);
+INSERT INTO modification VALUES (0,'create'), (1,'update'), (2,'delete');
+
+
+
+-- Now create the table that holds different parameter data types.
+CREATE TABLE parameter_data_type (
+ id smallint NOT NULL,
+ name VARCHAR(32) NOT NULL,
+ PRIMARY KEY (id)
+);
+INSERT INTO parameter_data_type VALUES
+ (0,'integer'),
+ (1,'real'),
+ (2,'boolean'),
+ (4,'string');
+
+
+
+-- This table doesn't exist in MySQL. However, it's nice to have an enum that explains what the values
+-- in ddns_replace_client_name field in the dhcp{4,6}_shared_network table means.
+CREATE TABLE ddns_replace_client_name_types (
+ type INT8 PRIMARY KEY NOT NULL,
+ name VARCHAR(32)
+);
+-- See enum ReplaceClientNameMode in src/lib/dhcpsrv/d2_client_cfg.h
+INSERT INTO ddns_replace_client_name_types (type, name) VALUES
+ (0, 'RCM_NEVER'),
+ (1, 'RCM_ALWAYS'),
+ (2, 'RCM_WHEN_PRESENT'),
+ (3, 'RCM_WHEN_NOT_PRESENT');
+
+
+
+-- Create table for DHCPv6 servers
+CREATE TABLE dhcp6_server (
+ id SERIAL PRIMARY KEY NOT NULL,
+ tag VARCHAR(64) NOT NULL,
+ description TEXT DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE(tag)
+);
+CREATE INDEX dhcp6_server_idx1 ON dhcp6_server (modification_ts);
+CREATE UNIQUE INDEX dhcp6_server_idx2 ON dhcp6_server(tag);
+CREATE TRIGGER dhcp6_server_modification_ts_update
+ AFTER UPDATE ON dhcp6_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+INSERT INTO dhcp6_server (tag, description) VALUES ('all','special type: all servers');
+
+-- Create a table for storing IPv6 shared networks
+CREATE TABLE dhcp6_shared_network (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) UNIQUE NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ preferred_lifetime BIGINT DEFAULT NULL,
+ rapid_commit BOOLEAN DEFAULT NULL,
+ rebind_timer BIGINT DEFAULT NULL,
+ relay TEXT DEFAULT NULL,
+ renew_timer BIGINT DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ valid_lifetime BIGINT DEFAULT NULL,
+ calculate_tee_times BOOLEAN DEFAULT NULL,
+ t1_percent float DEFAULT NULL,
+ t2_percent float DEFAULT NULL,
+ interface_id BYTEA DEFAULT NULL, -- 128 bytes
+ min_preferred_lifetime BIGINT DEFAULT NULL,
+ max_preferred_lifetime BIGINT DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ ddns_send_updates BOOLEAN DEFAULT NULL,
+ ddns_override_no_update BOOLEAN DEFAULT NULL,
+ ddns_override_client_update BOOLEAN DEFAULT NULL,
+ ddns_replace_client_name INT8 DEFAULT NULL,
+ ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL,
+ reservations_global BOOLEAN DEFAULT NULL,
+ reservations_in_subnet BOOLEAN DEFAULT NULL,
+ reservations_out_of_pool BOOLEAN DEFAULT NULL,
+ cache_threshold float DEFAULT NULL,
+ cache_max_age BIGINT DEFAULT NULL,
+ CONSTRAINT fk_ddns_replace_client_name FOREIGN KEY (ddns_replace_client_name)
+ REFERENCES ddns_replace_client_name_types (type)
+);
+CREATE INDEX dhcp6_shared_network_idx1 ON dhcp6_shared_network (name);
+
+CREATE TRIGGER dhcp6_shared_network_modification_ts_update
+ AFTER UPDATE ON dhcp6_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Now we need to create a relationship between defined shared networks and the servers
+CREATE TABLE dhcp6_shared_network_server (
+ shared_network_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (shared_network_id, server_id),
+ CONSTRAINT fk_dhcp6_shared_network_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_shared_network_server_shared_network_id FOREIGN KEY (shared_network_id)
+ REFERENCES dhcp6_shared_network (id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX dhcp6_shared_network_server_idx1 ON dhcp6_shared_network_server (modification_ts);
+CREATE INDEX dhcp6_shared_network_server_idx2 ON dhcp6_shared_network_server (server_id);
+
+
+
+-- Create a list of IPv6 subnets
+CREATE TABLE dhcp6_subnet (
+ subnet_id BIGINT PRIMARY KEY NOT NULL,
+ subnet_prefix VARCHAR(64) UNIQUE NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ preferred_lifetime BIGINT DEFAULT NULL,
+ rapid_commit BOOLEAN DEFAULT NULL,
+ rebind_timer BIGINT DEFAULT NULL,
+ relay TEXT DEFAULT NULL,
+ renew_timer BIGINT DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ shared_network_name VARCHAR(128) DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ valid_lifetime BIGINT DEFAULT NULL,
+ calculate_tee_times BOOLEAN DEFAULT NULL,
+ t1_percent float DEFAULT NULL,
+ t2_percent float DEFAULT NULL,
+ interface_id BYTEA DEFAULT NULL,
+ min_preferred_lifetime BIGINT DEFAULT NULL,
+ max_preferred_lifetime BIGINT DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ ddns_send_updates BOOLEAN DEFAULT NULL,
+ ddns_override_no_update BOOLEAN DEFAULT NULL,
+ ddns_override_client_update BOOLEAN DEFAULT NULL,
+ ddns_replace_client_name INT8 DEFAULT NULL,
+ ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL,
+ reservations_global BOOLEAN DEFAULT NULL,
+ reservations_in_subnet BOOLEAN DEFAULT NULL,
+ reservations_out_of_pool BOOLEAN DEFAULT NULL,
+ cache_threshold float DEFAULT NULL,
+ cache_max_age BIGINT DEFAULT NULL,
+ CONSTRAINT fk_dhcp6_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network (name) ON DELETE SET NULL ON UPDATE NO ACTION,
+ CONSTRAINT fk_ddns_replace_client_name FOREIGN KEY (ddns_replace_client_name)
+ REFERENCES ddns_replace_client_name_types (type)
+);
+
+CREATE TRIGGER dhcp6_subnet_modification_ts_update
+ AFTER UPDATE ON dhcp6_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+CREATE INDEX dhcp6_subnet_idx1 ON dhcp6_subnet (modification_ts);
+CREATE INDEX dhcp6_subnet_idx2 ON dhcp6_subnet (shared_network_name);
+
+
+
+-- Create a table that holds all address pools in IPv6.
+CREATE TABLE dhcp6_pool (
+ id SERIAL PRIMARY KEY NOT NULL,
+ start_address inet NOT NULL,
+ end_address inet NOT NULL,
+ subnet_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ client_class VARCHAR(128) DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ CONSTRAINT fk_dhcp6_pool_subnet_id FOREIGN KEY (subnet_id) REFERENCES dhcp6_subnet (subnet_id)
+);
+CREATE INDEX dhcp6_pool_idx1 ON dhcp6_pool (modification_ts);
+CREATE INDEX dhcp6_pool_idx2 ON dhcp6_pool (subnet_id);
+
+CREATE TRIGGER dhcp6_pool_modification_ts_update
+ AFTER UPDATE ON dhcp6_pool
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- And now the same, but for PD pools.
+CREATE TABLE dhcp6_pd_pool (
+ id SERIAL PRIMARY KEY NOT NULL,
+ prefix VARCHAR(45) NOT NULL,
+ prefix_length SMALLINT NOT NULL,
+ delegated_prefix_length SMALLINT NOT NULL,
+ subnet_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ excluded_prefix VARCHAR(45) DEFAULT NULL,
+ excluded_prefix_length SMALLINT NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ CONSTRAINT fk_dhcp6_pd_pool_subnet_id FOREIGN KEY (subnet_id) REFERENCES dhcp6_subnet(subnet_id)
+);
+
+CREATE INDEX dhcp6_pd_pool_idx1 ON dhcp6_pd_pool (modification_ts);
+CREATE INDEX dhcp6_pd_pool_idx2 ON dhcp6_pd_pool (subnet_id);
+CREATE TRIGGER dhcp6_pd_pool_modification_ts_update
+ AFTER UPDATE ON dhcp6_pd_pool
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+CREATE TABLE dhcp6_subnet_server (
+ subnet_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (subnet_id, server_id),
+ CONSTRAINT fk_dhcp6_subnet_server_server_id
+ FOREIGN KEY (server_id) REFERENCES dhcp6_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_subnet_server_subnet_id
+ FOREIGN KEY (subnet_id) REFERENCES dhcp6_subnet (subnet_id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ UNIQUE (subnet_id, server_id)
+);
+CREATE INDEX dhcp6_subnet_server_idx1 ON dhcp6_subnet_server(server_id);
+CREATE INDEX dhcp6_subnet_server_idx2 ON dhcp6_subnet_server(modification_ts);
+CREATE TRIGGER dhcp6_subnet_server_modification_ts_update
+ AFTER UPDATE ON dhcp6_subnet_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Create table for storing global DHCPv6 parameters.
+CREATE TABLE dhcp6_global_parameter (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ value TEXT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ parameter_type SMALLINT NOT NULL,
+ CONSTRAINT fk_dhcp6_global_parameter_type FOREIGN KEY (parameter_type) REFERENCES parameter_data_type(id)
+);
+
+CREATE INDEX key_dhcp6_global_parameter_idx1 ON dhcp6_global_parameter(modification_ts);
+CREATE INDEX key_dhcp6_global_parameter_idx2 ON dhcp6_global_parameter(name);
+
+CREATE TRIGGER dhcp6_global_parameter_modification_ts_update
+ AFTER UPDATE ON dhcp6_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+CREATE TABLE dhcp6_global_parameter_server (
+ parameter_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (parameter_id, server_id),
+ CONSTRAINT fk_dhcp6_global_parameter_server_parameter_id FOREIGN KEY (parameter_id)
+ REFERENCES dhcp6_global_parameter(id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_global_parameter_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server(id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX key_dhcp6_global_parameter_server_idx1 ON dhcp6_global_parameter_server(modification_ts);
+CREATE TRIGGER dhcp6_global_parameter_server_modification_ts_update
+ AFTER UPDATE ON dhcp6_global_parameter_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- Create a table for storing DHCPv6 options.
+ALTER TABLE dhcp6_options
+ ADD COLUMN shared_network_name VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN pool_id BIGINT DEFAULT NULL,
+ ADD COLUMN pd_pool_id BIGINT DEFAULT NULL,
+ ADD CONSTRAINT fk_dhcp6_options_pd_pool FOREIGN KEY (pd_pool_id)
+ REFERENCES dhcp6_pd_pool(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ ADD CONSTRAINT fk_dhcp6_options_pool FOREIGN KEY (pool_id)
+ REFERENCES dhcp6_pool (id) ON DELETE CASCADE ON UPDATE CASCADE,
+ ADD CONSTRAINT fk_dhcp6_options_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network (name) ON DELETE CASCADE ON UPDATE CASCADE;
+
+CREATE TRIGGER dhcp6_options_modification_ts_update
+ AFTER UPDATE ON dhcp6_options
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+-- Now create a table for associating defined options with servers.
+CREATE TABLE dhcp6_options_server (
+ option_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (option_id, server_id),
+ CONSTRAINT fk_dhcp6_options_server_option_id FOREIGN KEY (option_id)
+ REFERENCES dhcp6_options (option_id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_options_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX dhcp6_options_server_idx1 ON dhcp6_options_server(server_id);
+CREATE INDEX dhcp6_options_server_idx2 ON dhcp6_options_server(modification_ts);
+CREATE TRIGGER dhcp6_options_server_modification_ts_update
+ AFTER UPDATE ON dhcp6_options_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- This table is for storing IPv6 option definitions
+CREATE TABLE dhcp6_option_def (
+ id SERIAL PRIMARY KEY UNIQUE NOT NULL,
+ code SMALLINT NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ space VARCHAR(128) NOT NULL,
+ type SMALLINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ is_array BOOLEAN NOT NULL,
+ encapsulate VARCHAR(128) NOT NULL,
+ record_types VARCHAR DEFAULT NULL,
+ user_context JSON DEFAULT NULL
+);
+CREATE INDEX dhcp6_option_def_idx1 ON dhcp6_option_def(modification_ts);
+CREATE INDEX dhcp6_option_def_idx2 ON dhcp6_option_def(code, space);
+CREATE TRIGGER dhcp6_option_def_modification_ts_update
+ AFTER UPDATE ON dhcp6_option_def
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- and another table for storing relationship between option definitions and servers.
+CREATE TABLE dhcp6_option_def_server (
+ option_def_id BIGINT NOT NULL REFERENCES dhcp6_option_def (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ server_id BIGINT NOT NULL REFERENCES dhcp6_server (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (option_def_id, server_id)
+);
+CREATE TRIGGER dhcp6_option_def_server_modification_ts_update
+ AFTER UPDATE ON dhcp6_option_def_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- Now create two tables for audit revisions...
+CREATE TABLE dhcp6_audit_revision (
+ id SERIAL PRIMARY KEY NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ log_message TEXT DEFAULT NULL,
+ server_id BIGINT DEFAULT NULL
+);
+CREATE TRIGGER dhcp6_audit_revision_modification_ts_update
+ AFTER UPDATE ON dhcp6_audit_revision
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- ... and the DHCPv6 audit itself.
+CREATE TABLE dhcp6_audit (
+ id SERIAL UNIQUE NOT NULL,
+ object_type VARCHAR(256) NOT NULL,
+ object_id BIGINT NOT NULL,
+ modification_type SMALLINT NOT NULL,
+ revision_id BIGINT NOT NULL,
+ CONSTRAINT fk_dhcp6_audit_modification_type FOREIGN KEY (modification_type)
+ REFERENCES modification (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_audit_revision FOREIGN KEY (revision_id)
+ REFERENCES dhcp6_audit_revision (id) ON DELETE NO ACTION ON UPDATE CASCADE
+);
+CREATE TRIGGER dhcp6_audit_modification_ts_update
+ AFTER UPDATE ON dhcp6_audit
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+CREATE INDEX dhcp6_audit_idx1 ON dhcp6_audit (modification_type);
+CREATE INDEX dhcp6_audit_idx2 ON dhcp6_audit (revision_id);
+
+
+-- Create table for DHCPv4 servers
+CREATE TABLE dhcp4_server (
+ id SERIAL PRIMARY KEY NOT NULL,
+ tag VARCHAR(64) NOT NULL,
+ description TEXT DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE(tag)
+);
+CREATE INDEX dhcp4_server_modification_ts ON dhcp6_server (modification_ts);
+CREATE TRIGGER dhcp4_server_modification_ts_update
+ AFTER UPDATE ON dhcp4_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+INSERT INTO dhcp4_server (tag, description) VALUES ('all','special type: all servers');
+
+-- Create table for storing global DHCPv4 parameters.
+CREATE TABLE dhcp4_global_parameter (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ value TEXT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ parameter_type SMALLINT NOT NULL,
+ CONSTRAINT fk_dhcp6_global_parameter_type FOREIGN KEY (parameter_type) REFERENCES parameter_data_type(id)
+);
+CREATE INDEX dhcp4_global_parameter_idx1 ON dhcp4_global_parameter(modification_ts);
+CREATE INDEX dhcp4_global_parameter_idx2 ON dhcp4_global_parameter(name);
+
+CREATE TRIGGER dhcp4_global_parameter_modification_ts_update
+ AFTER UPDATE ON dhcp4_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+-- and create mapping for the global DHCPv4 parameters mapping to servers
+CREATE TABLE dhcp4_global_parameter_server (
+ parameter_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (parameter_id, server_id),
+ CONSTRAINT fk_dhcp4_global_parameter_server_parameter_id FOREIGN KEY (parameter_id)
+ REFERENCES dhcp4_global_parameter(id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_global_parameter_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server(id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX key_dhcp4_global_parameter_idx1 ON dhcp4_global_parameter_server(modification_ts);
+CREATE TRIGGER dhcp4_global_parameter_server_modification_ts_update
+ AFTER UPDATE ON dhcp4_global_parameter_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- Create a table for storing IPv4 shared networks
+CREATE TABLE dhcp4_shared_network (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) UNIQUE NOT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ match_client_id BOOLEAN DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ rebind_timer BIGINT DEFAULT NULL,
+ relay TEXT DEFAULT NULL,
+ renew_timer BIGINT DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ valid_lifetime BIGINT DEFAULT NULL,
+ authoritative BOOLEAN DEFAULT NULL,
+ calculate_tee_times BOOLEAN DEFAULT NULL,
+ t1_percent float DEFAULT NULL,
+ t2_percent float DEFAULT NULL,
+ boot_file_name VARCHAR(128) DEFAULT NULL,
+ next_server inet DEFAULT NULL, -- let's use type inet
+ server_hostname VARCHAR(64) DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ ddns_send_updates BOOLEAN DEFAULT NULL,
+ ddns_override_no_update BOOLEAN DEFAULT NULL,
+ ddns_override_client_update BOOLEAN DEFAULT NULL,
+ ddns_replace_client_name INT8 DEFAULT NULL,
+ ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL,
+ reservations_global BOOLEAN DEFAULT NULL,
+ reservations_in_subnet BOOLEAN DEFAULT NULL,
+ reservations_out_of_pool BOOLEAN DEFAULT NULL,
+ cache_threshold float DEFAULT NULL,
+ cache_max_age BIGINT DEFAULT NULL,
+ CONSTRAINT fk_ddns_replace_client_name FOREIGN KEY (ddns_replace_client_name)
+ REFERENCES ddns_replace_client_name_types (type)
+);
+
+CREATE UNIQUE INDEX dhcp4_shared_network_idx1 ON dhcp4_shared_network (name);
+CREATE INDEX dhcp4_shared_network_idx2 ON dhcp4_shared_network (modification_ts);
+
+CREATE TRIGGER dhcp4_shared_network_modification_ts_update
+ AFTER UPDATE ON dhcp4_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Now we need to create a relationship between defined shared networks and the servers
+CREATE TABLE dhcp4_shared_network_server (
+ shared_network_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (shared_network_id, server_id),
+ CONSTRAINT fk_dhcp4_shared_network_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_shared_network_server_shared_network_id FOREIGN KEY (shared_network_id)
+ REFERENCES dhcp4_shared_network (id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX dhcp4_shared_network_server_idx1 ON dhcp4_shared_network_server (modification_ts);
+CREATE INDEX dhcp4_shared_network_server_idx2 ON dhcp4_shared_network_server (server_id);
+
+
+
+-- Create a list of IPv4 subnets
+CREATE TABLE dhcp4_subnet (
+ subnet_id BIGINT PRIMARY KEY NOT NULL,
+ subnet_prefix VARCHAR(64) UNIQUE NOT NULL,
+ interface_4o6 VARCHAR(128) DEFAULT NULL,
+ interface_id_4o6 VARCHAR(128) DEFAULT NULL,
+ subnet_4o6 VARCHAR(64) DEFAULT NULL,
+ boot_file_name VARCHAR(128) DEFAULT NULL,
+ client_class VARCHAR(128) DEFAULT NULL,
+ interface VARCHAR(128) DEFAULT NULL,
+ match_client_id BOOLEAN DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ next_server inet DEFAULT NULL,
+ rebind_timer BIGINT DEFAULT NULL,
+ relay TEXT DEFAULT NULL,
+ renew_timer BIGINT DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ server_hostname VARCHAR(64) DEFAULT NULL,
+ shared_network_name VARCHAR(128) DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ valid_lifetime BIGINT DEFAULT NULL,
+ authoritative BOOLEAN DEFAULT NULL,
+ calculate_tee_times BOOLEAN DEFAULT NULL,
+ t1_percent float DEFAULT NULL,
+ t2_percent float DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ ddns_send_updates BOOLEAN DEFAULT NULL,
+ ddns_override_no_update BOOLEAN DEFAULT NULL,
+ ddns_override_client_update BOOLEAN DEFAULT NULL,
+ ddns_replace_client_name INT8 DEFAULT NULL,
+ ddns_generated_prefix VARCHAR(255) DEFAULT NULL,
+ ddns_qualifying_suffix VARCHAR(255) DEFAULT NULL,
+ reservations_global BOOLEAN DEFAULT NULL,
+ reservations_in_subnet BOOLEAN DEFAULT NULL,
+ reservations_out_of_pool BOOLEAN DEFAULT NULL,
+ cache_threshold float DEFAULT NULL,
+ cache_max_age BIGINT DEFAULT NULL,
+ CONSTRAINT fk_dhcp4_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network (name) ON DELETE SET NULL ON UPDATE NO ACTION,
+ CONSTRAINT fk_ddns_replace_client_name FOREIGN KEY (ddns_replace_client_name)
+ REFERENCES ddns_replace_client_name_types (type)
+);
+
+CREATE TRIGGER dhcp4_subnet_modification_ts_update
+ AFTER UPDATE ON dhcp4_subnet
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+CREATE INDEX dhcp4_subnet_idx1 ON dhcp4_subnet (modification_ts);
+CREATE INDEX dhcp4_subnet_idx2 ON dhcp4_subnet (shared_network_name);
+
+
+
+CREATE TABLE dhcp4_subnet_server (
+ subnet_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (subnet_id, server_id),
+ CONSTRAINT fk_dhcp6_subnet_server_server_id
+ FOREIGN KEY (server_id) REFERENCES dhcp6_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp6_subnet_server_subnet_id
+ FOREIGN KEY (subnet_id) REFERENCES dhcp6_subnet (subnet_id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ UNIQUE (subnet_id, server_id)
+);
+CREATE INDEX dhcp4_subnet_server_idx1 ON dhcp4_subnet_server(server_id);
+CREATE INDEX dhcp4_subnet_server_idx2 ON dhcp4_subnet_server(modification_ts);
+CREATE TRIGGER dhcp4_subnet_server_modification_ts_update
+ AFTER UPDATE ON dhcp4_subnet_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Create a table that holds all address pools in IPv4.
+CREATE TABLE dhcp4_pool (
+ id SERIAL PRIMARY KEY NOT NULL,
+ start_address inet NOT NULL,
+ end_address inet NOT NULL,
+ subnet_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ client_class VARCHAR(128) DEFAULT NULL,
+ require_client_classes TEXT DEFAULT NULL,
+ user_context JSON DEFAULT NULL,
+ CONSTRAINT fk_dhcp4_pool_subnet_id FOREIGN KEY (subnet_id) REFERENCES dhcp4_subnet (subnet_id)
+);
+CREATE INDEX dhcp4_pool_idx1 ON dhcp4_pool (modification_ts);
+CREATE INDEX dhcp4_pool_idx2 ON dhcp4_pool (subnet_id);
+
+CREATE TRIGGER dhcp4_pool_modification_ts_update
+ AFTER UPDATE ON dhcp4_pool
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- Create a table for storing DHCPv4 options.
+ALTER TABLE dhcp4_options
+ ADD COLUMN shared_network_name VARCHAR(128) DEFAULT NULL,
+ ADD COLUMN pool_id BIGINT DEFAULT NULL,
+ ADD COLUMN modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ ADD CONSTRAINT fk_dhcp4_options_pool FOREIGN KEY (pool_id)
+ REFERENCES dhcp4_pool (id) ON DELETE CASCADE ON UPDATE CASCADE,
+ ADD CONSTRAINT fk_dhcp4_options_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network (name) ON DELETE CASCADE ON UPDATE CASCADE;
+
+CREATE TRIGGER dhcp4_options_modification_ts_update
+ AFTER UPDATE ON dhcp4_options
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Now create a table for associating defined v4 options with servers.
+CREATE TABLE dhcp4_options_server (
+ option_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (option_id, server_id),
+ CONSTRAINT fk_dhcp4_options_server_option_id FOREIGN KEY (option_id)
+ REFERENCES dhcp4_options (option_id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_options_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id) ON DELETE CASCADE ON UPDATE NO ACTION
+);
+CREATE INDEX dhcp4_options_server_idx1 ON dhcp4_options_server(server_id);
+CREATE INDEX dhcp4_options_server_idx2 ON dhcp4_options_server(modification_ts);
+CREATE TRIGGER dhcp4_options_server_modification_ts_update
+ AFTER UPDATE ON dhcp4_options_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- This table is for storing IPv4 option definitions
+CREATE TABLE dhcp4_option_def (
+ id SERIAL PRIMARY KEY UNIQUE NOT NULL,
+ code SMALLINT NOT NULL,
+ name VARCHAR(128) NOT NULL,
+ space VARCHAR(128) NOT NULL,
+ type SMALLINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ is_array BOOLEAN NOT NULL,
+ encapsulate VARCHAR(128) NOT NULL,
+ record_types VARCHAR DEFAULT NULL,
+ user_context JSON DEFAULT NULL
+);
+CREATE INDEX dhcp4_option_def_idx1 ON dhcp4_option_def(modification_ts);
+CREATE INDEX dhcp4_option_def_idx2 ON dhcp4_option_def(code, space);
+CREATE TRIGGER dhcp4_option_def_modification_ts_update
+ AFTER UPDATE ON dhcp4_option_def
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- and another table for storing relationship between option definitions and servers.
+CREATE TABLE dhcp4_option_def_server (
+ option_def_id BIGINT NOT NULL REFERENCES dhcp6_option_def (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ server_id BIGINT NOT NULL REFERENCES dhcp4_server (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (option_def_id, server_id)
+);
+CREATE TRIGGER dhcp4_option_def_server_modification_ts_update
+ AFTER UPDATE ON dhcp4_option_def_server
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+
+-- Now create two tables for audit revisions...
+CREATE TABLE dhcp4_audit_revision (
+ id SERIAL PRIMARY KEY NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ log_message TEXT DEFAULT NULL,
+ server_id BIGINT DEFAULT NULL
+);
+CREATE TRIGGER dhcp4_audit_revision_modification_ts_update
+ AFTER UPDATE ON dhcp4_audit_revision
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+
+
+-- ... and the DHCPv4 audit itself.
+CREATE TABLE dhcp4_audit (
+ id SERIAL UNIQUE NOT NULL,
+ object_type VARCHAR(256) NOT NULL,
+ object_id BIGINT NOT NULL,
+ modification_type SMALLINT NOT NULL,
+ revision_id BIGINT NOT NULL,
+ CONSTRAINT fk_dhcp4_audit_modification_type FOREIGN KEY (modification_type)
+ REFERENCES modification (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ CONSTRAINT fk_dhcp4_audit_revision FOREIGN KEY (revision_id)
+ REFERENCES dhcp4_audit_revision (id) ON DELETE NO ACTION ON UPDATE CASCADE
+);
+CREATE TRIGGER dhcp4_audit_modification_ts_update
+ AFTER UPDATE ON dhcp4_audit
+ FOR EACH ROW EXECUTE PROCEDURE modification_ts_update();
+CREATE INDEX dhcp4_audit_idx1 ON dhcp4_audit (modification_type);
+CREATE INDEX dhcp4_audit_idx2 ON dhcp4_audit (revision_id);
+
+-- Stores a TEXT value to a session variable
+-- name name of session variable to set
+-- value TEXT value to store
+CREATE OR REPLACE FUNCTION set_session_value(name text, value TEXT)
+RETURNS VOID
+AS \$\$
+DECLARE
+BEGIN
+ PERFORM set_config(name, value, false);
+ RETURN;
+
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'set_session_value(%) : value:[%] failed, sqlstate: %', name, value, sqlstate;
+END;\$\$
+LANGUAGE plpgsql;
+
+-- Stores a BIGINT value to a session variable
+-- Note the value converted to TEXT and then stored as Postgresql does
+-- not support any other data type in session variables.
+-- name name of session variable to set
+-- value BIGINT value to store
+CREATE OR REPLACE FUNCTION set_session_value(name text, value BIGINT)
+RETURNS VOID
+AS \$\$
+BEGIN
+ PERFORM set_config(name, cast(value as text), false);
+ RETURN;
+
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'set_session_value(%) : value:[%] failed, sqlstate: %', name, value, sqlstate;
+END;\$\$
+LANGUAGE plpgsql;
+
+-- Stores a BOOLEAN value to a session variable
+-- Note the value converted to TEXT and then stored as Postgresql does
+-- not support any other data type in session variables.
+-- name name of session variable to set
+-- value BOOLEAN value to store
+CREATE OR REPLACE FUNCTION set_session_value(name text, value BOOLEAN)
+RETURNS VOID
+AS \$\$
+BEGIN
+ PERFORM set_config(name, cast(value as text), false);
+ RETURN;
+
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'set_session_value(%) : value:[%] failed, sqlstate: %', name, value, sqlstate;
+END;\$\$
+LANGUAGE plpgsql;
+
+
+-- Fetches a text value from the session configuration.
+-- param name name of the session variable to fetch
+-- If the name is not found it returns NULL.
+-- Postgresql allows you to store custom session values
+-- but throws an exception if they have not first been
+-- set. This allows us to be a bit more graceful.
+CREATE OR REPLACE FUNCTION get_session_value(name TEXT)
+RETURNS TEXT
+AS \$\$
+DECLARE
+ text_value TEXT := '';
+BEGIN
+ text_value = current_setting(name);
+ RETURN(text_value);
+
+ EXCEPTION
+ WHEN undefined_object THEN
+ -- Variable has not been initialized so return NULL
+ RETURN NULL;
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'get_session_value(%, TEXT) failed, sqlstate: %', name, sqlstate;
+END;\$\$
+LANGUAGE plpgsql;
+
+-- Fetches an BIGINT value from the session configuration.
+-- param name name of the session variable to fetch
+-- If the name is not found it returns zero.
+CREATE OR REPLACE FUNCTION get_session_big_int(name text)
+RETURNS BIGINT
+AS \$\$
+DECLARE
+ int_value BIGINT := 0;
+ text_value TEXT := '';
+BEGIN
+ text_value = get_session_value(name);
+ IF text_value is NULL or text_value = '' THEN
+ RETURN(0);
+ END IF;
+
+ int_value = cast(text_value as BIGINT);
+ RETURN(int_value);
+
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'get_session_big_int(%) failed - text:[%] , sqlstate: %', name, text_value, sqlstate;
+
+END;\$\$
+LANGUAGE plpgsql;
+
+-- Fetches an BOOLEAN value from the session configuration.
+-- param name name of the session variable to fetch
+-- If the name is not found it returns zero.
+CREATE OR REPLACE FUNCTION get_session_boolean(name text)
+RETURNS BOOLEAN
+AS \$\$
+DECLARE
+ bool_value BOOLEAN := false;
+ text_value TEXT := '';
+BEGIN
+ text_value = get_session_value(name);
+ IF text_value is NULL or text_value = '' THEN
+ RETURN(false);
+ END IF;
+
+ bool_value = cast(text_value as BOOLEAN);
+ RETURN(bool_value);
+
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'get_session_boolean(%) failed - text:[%] , sqlstate: %', name, text_value, sqlstate;
+
+END;\$\$
+LANGUAGE plpgsql;
+
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp4_audit_revision table and sets appropriate session
+-- variables to be used while creating the audit entries
+-- by triggers. This procedure should be called at the
+-- beginning of a transaction which modifies configuration
+-- data in the database, e.g. when new subnet is added.
+--
+-- Parameters:
+-- - audit_ts timestamp to be associated with the audit
+-- revision.
+-- - server_tag is used to retrieve the server_id which
+-- associates the changes applied with the particular
+-- server or all servers.
+-- - audit_log_message is a log message associates with
+-- the audit revision.
+-- - cascade_transaction is assigned to a session
+-- variable which is used in some triggers to determine
+-- if the audit entry should be created for them or
+-- not. Specifically, this is used when DHCP options
+-- are inserted, updated or deleted. If such modification
+-- is a part of the larger change (e.g. change in the
+-- subnet the options belong to) the dedicated audit
+-- entry for options must not be created. On the other
+-- hand, if the global option is being added, the
+-- audit entry for the option must be created because
+-- it is the sole object modified in that case.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createAuditRevisionDHCP4(audit_ts TIMESTAMP WITH TIME ZONE,
+ server_tag VARCHAR(64),
+ audit_log_message TEXT,
+ cascade_transaction BOOLEAN)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ disable_audit BOOLEAN := false;
+ audit_revision_id BIGINT;
+ srv_id BIGINT;
+BEGIN
+ -- Fetch session value for disable_audit.
+ disable_audit := get_session_boolean('kea.disable_audit');
+ IF disable_audit = false THEN
+ SELECT id INTO srv_id FROM dhcp4_server WHERE tag = server_tag;
+ INSERT INTO dhcp4_audit_revision (modification_ts, server_id, log_message)
+ VALUES (audit_ts, srv_id, audit_log_message) returning id INTO audit_revision_id;
+
+ -- Update pertinent session variables.
+ PERFORM set_session_value('kea.audit_revision_id', audit_revision_id);
+ PERFORM set_session_value('kea.cascade_transaction', cascade_transaction);
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp4_audit table. It should be called from the
+-- triggers of the tables where the config modifications
+-- are applied. The audit_revision_id variable contains
+-- the revision id to be placed in the audit entries.
+--
+-- The following parameters are passed to this procedure:
+-- - object_type_val: name of the table to be associated
+-- with the applied changes.
+-- - object_id_val: identifier of the modified object in
+-- that table.
+-- - modification_type_val: string value indicating the
+-- type of the change, i.e. 'create', 'update' or
+-- 'delete'.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- ----------------------------------------------------
+CREATE OR REPLACE FUNCTION createAuditEntryDHCP4(object_type_val VARCHAR(256),
+ object_id_val BIGINT,
+ modification_type_val VARCHAR(32))
+RETURNS VOID
+LANGUAGE plpgsql
+as \$\$
+DECLARE
+ audit_revision_id BIGINT;
+ disable_audit BOOLEAN := false;
+BEGIN
+ -- Fetch session value for disable_audit.
+ disable_audit := get_session_boolean('kea.disable_audit');
+
+ IF disable_audit IS NULL OR disable_audit = false THEN
+ -- Fetch session value most recently created audit_revision_id.
+ audit_revision_id := get_session_big_int('kea.audit_revision_id');
+ INSERT INTO dhcp4_audit (object_type, object_id, modification_type, revision_id)
+ VALUES (object_type_val, object_id_val,
+ (SELECT id FROM modification WHERE modification_type = modification_type_val),
+ audit_revision_id);
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv4 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp4_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) UNIQUE NOT NULL,
+ test TEXT,
+ next_server INET DEFAULT NULL,
+ server_hostname VARCHAR(128) DEFAULT NULL,
+ boot_file_name VARCHAR(512) DEFAULT NULL,
+ only_if_required BOOLEAN NOT NULL DEFAULT false,
+ valid_lifetime BIGINT DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ depend_on_known_directly BOOLEAN NOT NULL DEFAULT false,
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX key_dhcp4_client_class_modification_ts on dhcp4_client_class (modification_ts);
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp4_client_class table has a corresponding row
+-- in the dhcp4_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp4_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp4_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_indirectly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp4_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_order (
+ class_id BIGINT PRIMARY KEY NOT NULL,
+ order_index BIGINT NOT NULL,
+ depend_on_known_indirectly BOOLEAN NOT NULL DEFAULT false,
+ CONSTRAINT fk_dhcp4_client_class_order_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id) ON DELETE CASCADE
+);
+
+CREATE INDEX key_dhcp4_client_class_order_index on dhcp4_client_class_order (order_index);
+
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass4Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BIGINT := 0;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp4_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp4_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp4_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp4_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp4_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency AS d
+ INNER JOIN dhcp4_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- ON CONFLICT required 9.5 or later
+ UPDATE dhcp4_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+ RETURN;
+END;\$\$;
+
+-- -----------------------------------------------------------------------
+-- Trigger procedure to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_AINS() RETURNS trigger AS \$dhcp4_client_class_AINS\$
+BEGIN
+ PERFORM setClientClass4Order(NEW.id, NEW.follow_class_name, NULL);
+ PERFORM createAuditEntryDHCP4('dhcp4_client_class', NEW.id, 'create');
+ RETURN NULL;
+END;
+\$dhcp4_client_class_AINS\$
+LANGUAGE plpgsql;
+
+-- Create dhcp4_client_class insert trigger
+CREATE TRIGGER dhcp4_client_class_AINS
+ AFTER INSERT ON dhcp4_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_client_class_AINS();
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_AUPD() RETURNS trigger AS \$dhcp4_client_class_AUPD\$
+BEGIN
+ PERFORM set_session_value('kea.depend_on_known_directly', OLD.depend_on_known_directly);
+ PERFORM set_session_value('kea.client_class_id', NEW.id);
+ PERFORM setClientClass4Order(NEW.id, NEW.follow_class_name, OLD.follow_class_name);
+ PERFORM createAuditEntryDHCP4('dhcp4_client_class', NEW.id, 'update');
+ RETURN NULL;
+END;
+\$dhcp4_client_class_AUPD\$
+LANGUAGE plpgsql;
+
+-- Create dhcp4_client_class update insert trigger
+CREATE TRIGGER dhcp4_client_class_AUPD
+ AFTER UPDATE ON dhcp4_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_client_class_AUPD();
+
+-- -----------------------------------------------------------------------
+-- Trigger procedure to create the audit entry for client class delete.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_ADEL() RETURNS trigger AS \$dhcp4_client_class_ADEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_client_class', OLD.id, 'delete');
+ RETURN NULL;
+END;
+\$dhcp4_client_class_ADEL\$
+LANGUAGE plpgsql;
+
+-- Create dhcp4_client_class delete trigger
+CREATE TRIGGER dhcp4_client_class_ADEL
+ AFTER DELETE ON dhcp4_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_client_class_ADEL();
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp4_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp4_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_dependency (
+ class_id BIGINT PRIMARY KEY NOT NULL,
+ dependency_id BIGINT NOT NULL,
+
+ CONSTRAINT dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp4_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp4_client_class (id)
+);
+
+CREATE INDEX dhcp4_client_class_dependency_id_idx on dhcp4_client_class_dependency (dependency_id);
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - p_class_id id client class,
+-- - p_dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION checkDHCPv4ClientClassDependency(p_class_id BIGINT,
+ p_dependency_id BIGINT)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ class_index BIGINT;
+ dependency_index BIGINT;
+BEGIN
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF p_class_id IS NULL THEN
+ RAISE EXCEPTION 'Client class id must not be NULL.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ IF p_dependency_id IS NULL THEN
+ RAISE EXCEPTION 'Class dependency id must not be NULL.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Dependencies on self make no sense.
+ IF p_class_id = p_dependency_id THEN
+ RAISE EXCEPTION 'Client class must not have dependency on self.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check position of our class in the hierarchy.
+ SELECT o.order_index INTO class_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = p_class_id;
+
+ IF class_index IS NULL THEN
+ RAISE EXCEPTION 'Client class with id % does not exist.', p_class_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check position of the dependency.
+ SELECT o.order_index INTO dependency_index FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = p_dependency_id;
+
+ IF dependency_index IS NULL THEN
+ RAISE EXCEPTION 'Dependency class with id % does not exist.', p_dependency_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ RAISE EXCEPTION
+ 'Client class with id % must not depend on class defined later with id %',
+ p_class_id, p_dependency_id USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp4_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_server AS t1
+ LEFT JOIN dhcp4_client_class_server AS t2
+ ON t2.class_id = p_dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = p_class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unmet dependencies for client class with id %', class_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+ RETURN;
+END;\$\$;
+
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_check_dependency_BINS()
+ RETURNS trigger AS \$dhcp4_client_class_check_dependency_BINS\$
+BEGIN
+ PERFORM checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NULL;
+END;
+\$dhcp4_client_class_check_dependency_BINS\$
+LANGUAGE plpgsql;
+
+-- Create dhcp4_client_class_check_dependency_BINS before insert trigger.
+CREATE TRIGGER dhcp4_client_class_check_dependency_BINS
+ BEFORE INSERT ON dhcp4_client_class_dependency
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_client_class_check_dependency_BINS();
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION updateDHCPv4ClientClassKnownDependency(client_class_id BIGINT,
+ dependency_id BIGINT)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ dependency BOOLEAN;
+BEGIN
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SELECT depend_on_known_directly INTO dependency FROM dhcp4_client_class
+ WHERE id = dependency_id;
+
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = false THEN
+ SELECT depend_on_known_indirectly INTO dependency FROM dhcp4_client_class_order
+ WHERE class_id = dependency_id;
+ END IF;
+
+ IF dependency = true THEN
+ UPDATE dhcp4_client_class_order
+ SET depend_on_known_indirectly = true
+ WHERE class_id = client_class_id;
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_dependency_AINS()
+ RETURNS TRIGGER AS \$dhcp4_client_class_dependency_AINS\$
+BEGIN
+ PERFORM updateDHCPv4ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NULL;
+END;
+\$dhcp4_client_class_dependency_AINS\$
+LANGUAGE plpgsql;
+
+-- Create dhcp4_client_class_check_dependency_AINS after insert trigger.
+CREATE TRIGGER dhcp4_client_class_dependency_AINS
+ AFTER INSERT ON dhcp4_client_class_dependency
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_client_class_dependency_AINS();
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv4 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION checkDHCPv4ClientClassKnownDependencyChange()
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ depended BOOLEAN := false;
+ depends BOOLEAN := false;
+ client_class_id BIGINT;
+ depend_on_known_directly BOOLEAN;
+ depend_on_known_indirectly BOOLEAN;
+BEGIN
+
+ -- Session variables are set upon a client class update.
+ client_class_id := get_session_big_int('kea.client_class_id');
+ IF client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency
+ WHERE dependency_id = client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ depend_on_known_directly := get_session_boolean('kea.depend_on_known_directly');
+ depend_on_known_indirectly := get_session_boolean('kea.depend_on_known_indirectly');
+ IF depend_on_known_directly = true OR depend_on_known_indirectly = true THEN
+ SET depended = true;
+ END IF;
+
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SELECT depend_on_known_directly INTO depends FROM dhcp4_client_class
+ WHERE id = client_class_id;
+
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = false THEN
+ SELECT depend_on_known_indirectly INTO depends FROM dhcp4_client_class_order
+ WHERE class_id = client_class_id;
+ END IF;
+
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ RAISE EXCEPTION 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+ END IF;
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv4 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp4_client_class_server (
+ class_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ CONSTRAINT fk_dhcp4_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp4_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id)
+);
+
+CREATE INDEX fk_dhcp4_client_class_server_id ON dhcp4_client_class_server (server_id);
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp6_audit_revision table and sets appropriate session
+-- variables to be used while creating the audit entries
+-- by triggers. This procedure should be called at the
+-- beginning of a transaction which modifies configuration
+-- data in the database, e.g. when new subnet is added.
+--
+-- Parameters:
+-- - audit_ts timestamp to be associated with the audit
+-- revision.
+-- - server_tag is used to retrieve the server_id which
+-- associates the changes applied with the particular
+-- server or all servers.
+-- - audit_log_message is a log message associates with
+-- the audit revision.
+-- - cascade_transaction is assigned to a session
+-- variable which is used in some triggers to determine
+-- if the audit entry should be created for them or
+-- not. Specifically, this is used when DHCP options
+-- are inserted, updated or deleted. If such modification
+-- is a part of the larger change (e.g. change in the
+-- subnet the options belong to) the dedicated audit
+-- entry for options must not be created. On the other
+-- hand, if the global option is being added, the
+-- audit entry for the option must be created because
+-- it is the sole object modified in that case.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createAuditRevisionDHCP6(audit_ts TIMESTAMP WITH TIME ZONE,
+ server_tag VARCHAR(64),
+ audit_log_message TEXT,
+ cascade_transaction BOOLEAN)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ disable_audit BOOLEAN := false;
+ audit_revision_id BIGINT;
+ srv_id BIGINT;
+BEGIN
+ -- Fetch session value for disable_audit.
+ disable_audit := get_session_boolean('kea.disable_audit');
+ IF disable_audit = false THEN
+ SELECT id INTO srv_id FROM dhcp6_server WHERE tag = server_tag;
+ INSERT INTO dhcp6_audit_revision (modification_ts, server_id, log_message)
+ VALUES (audit_ts, srv_id, audit_log_message) returning id INTO audit_revision_id;
+
+ -- Update pertinent session variables.
+ PERFORM set_session_value('kea.audit_revision_id', audit_revision_id);
+ PERFORM set_session_value('kea.cascade_transaction', cascade_transaction);
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- -----------------------------------------------------
+-- Stored procedure which creates a new entry in the
+-- dhcp6_audit table. It should be called from the
+-- triggers of the tables where the config modifications
+-- are applied. The audit_revision_id variable contains
+-- the revision id to be placed in the audit entries.
+--
+-- The following parameters are passed to this procedure:
+-- - object_type_val: name of the table to be associated
+-- with the applied changes.
+-- - object_id_val: identifier of the modified object in
+-- that table.
+-- - modification_type_val: string value indicating the
+-- type of the change, i.e. 'create', 'update' or
+-- 'delete'.
+-- Session variable disable_audit is used to disable
+-- the procedure when wiping the database during
+-- unit tests. This avoids issues with revision_id
+-- being null.
+-- ----------------------------------------------------
+CREATE OR REPLACE FUNCTION createAuditEntryDHCP6(object_type_val VARCHAR(256),
+ object_id_val BIGINT,
+ modification_type_val VARCHAR(32))
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ audit_revision_id BIGINT;
+ disable_audit BOOLEAN := false;
+BEGIN
+ -- Fetch session value for disable_audit.
+ disable_audit := get_session_boolean('kea.disable_audit');
+ IF disable_audit = false THEN
+ -- Fetch session value most recently created audit_revision_id.
+ audit_revision_id := get_session_big_int('kea.audit_revision_id');
+ INSERT INTO dhcp6_audit (object_type, object_id, modification_type, revision_id)
+ VALUES (object_type_val, object_id_val,
+ (SELECT id FROM modification WHERE modification_type = modification_type_val),
+ audit_revision_id);
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- -----------------------------------------------------------------------
+-- Create a table holding the DHCPv6 client classes. Most table
+-- columns map directly to respective client class properties in
+-- Kea configuration. The depend_on_known_directly column is
+-- explicitly set in an insert or update statement to indicate
+-- if the client class directly depends on KNOWN or UNKNOWN
+-- built-in classes. A caller should determine it by evaluating
+-- a test expression before inserting or updating the client
+-- class in the database. The nullable follow_class_name column
+-- can be used for positioning the inserted or updated client
+-- class within the class hierarchy. Set this column value to
+-- an existing class name, after which this class should be
+-- placed in the class hierarchy. See dhcp6_client_class_order
+-- description for the details of how classes are ordered.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class (
+ id SERIAL PRIMARY KEY NOT NULL,
+ name VARCHAR(128) UNIQUE NOT NULL,
+ test TEXT,
+ only_if_required BOOLEAN NOT NULL DEFAULT false,
+ valid_lifetime BIGINT DEFAULT NULL,
+ min_valid_lifetime BIGINT DEFAULT NULL,
+ max_valid_lifetime BIGINT DEFAULT NULL,
+ depend_on_known_directly BOOLEAN NOT NULL DEFAULT false,
+ follow_class_name VARCHAR(128) DEFAULT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX key_dhcp6_client_class_modification_ts on dhcp6_client_class (modification_ts);
+
+-- -----------------------------------------------------------------------
+-- Create a table for ordering client classes and holding information
+-- about indirect dependencies on KNOWN/UKNOWN built-in client classes.
+-- Each class in the dhcp6_client_class table has a corresponding row
+-- in the dhcp6_client_class_order table. A caller should not modify
+-- the contents of this table. Its entries are automatically created
+-- upon inserting or updating client classes in the dhcp6_client_classes
+-- using triggers. The order_index designates the position of the client
+-- class within the class hierarchy. If the follow_class_name value of
+-- the dhcp6_client_class table is set to NULL, the client class is
+-- appended at the end of the hierarchy. The assigned order_index
+-- value for that class is set to a maximum current value + 1.
+-- If the follow_client_class specifies a name of an existing class,
+-- the generated order_index is set to an id of that class + 1, and
+-- the order_index values of the later classes are incremented by 1.
+-- The depend_on_known_indirectly column holds a boolean value indicating
+-- whether the given class depends on KNOWN/UKNOWN built-in classes
+-- via other classes, i.e. it depends on classes that directly or
+-- indirectly depend on these built-ins. This value is auto-generated
+-- by a trigger on the dhcp6_client_class_dependency table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_order (
+ class_id BIGINT PRIMARY KEY NOT NULL,
+ order_index BIGINT NOT NULL,
+ depend_on_known_indirectly BOOLEAN NOT NULL DEFAULT false,
+ CONSTRAINT fk_dhcp6_client_class_order_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE
+);
+
+CREATE INDEX key_dhcp6_client_class_order_index on dhcp6_client_class_order (order_index);
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- new_follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - new_follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass6Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BIGINT := 0;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp6_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp6_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp6_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp6_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp6_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency AS d
+ INNER JOIN dhcp6_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM - note that ON CONFLICT requires PostgreSQL 9.5 or later.
+ UPDATE dhcp6_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, 0);
+ RETURN;
+END;\$\$;
+
+-- -----------------------------------------------------------------------
+-- Trigger procedure to position an inserted class within the class hierarchy
+-- and create audit.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_AINS() RETURNS trigger AS \$dhcp6_client_class_AINS\$
+BEGIN
+ PERFORM setClientClass6Order(NEW.id, NEW.follow_class_name, NULL);
+ PERFORM createAuditEntryDHCP6('dhcp6_client_class', NEW.id, 'create');
+ RETURN NULL;
+END;
+\$dhcp6_client_class_AINS\$
+LANGUAGE plpgsql;
+
+-- Create dhcp6_client_class insert trigger
+CREATE TRIGGER dhcp6_client_class_AINS
+ AFTER INSERT ON dhcp6_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_client_class_AINS();
+
+-- -----------------------------------------------------------------------
+-- Trigger to position an updated class within the class hierarchy,
+-- create audit and remember the direct dependency on the
+-- KNOWN/UNKNOWN built-in classes before the class update.
+-- When updating a client class, it is very important to ensure that
+-- its dependency on KNOWN or UNKNOWN built-in client classes is not
+-- changed. It is because there may be other classes that depend on
+-- these built-ins via this class. Changing the dependency would break
+-- the chain of dependencies for other classes. Here, we store the
+-- information about the dependency in the session variables. Their
+-- values will be compared with the new dependencies after an update.
+-- If they change, an error will be signaled.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_AUPD() RETURNS trigger AS \$dhcp6_client_class_AUPD\$
+BEGIN
+ PERFORM set_session_value('kea.depend_on_known_directly', OLD.depend_on_known_directly);
+ PERFORM set_session_value('kea.client_class_id', NEW.id);
+ PERFORM setClientClass6Order(NEW.id, NEW.follow_class_name, OLD.follow_class_name);
+ PERFORM createAuditEntryDHCP6('dhcp6_client_class', NEW.id, 'update');
+ RETURN NULL;
+END;
+\$dhcp6_client_class_AUPD\$
+LANGUAGE plpgsql;
+
+-- Create dhcp6_client_class update insert trigger
+CREATE TRIGGER dhcp6_client_class_AUPD
+ AFTER UPDATE ON dhcp6_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_client_class_AUPD();
+
+-- -----------------------------------------------------------------------
+-- Trigger procedure to create the audit entry for client class delete.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_ADEL() RETURNS trigger AS \$dhcp6_client_class_ADEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_client_class', OLD.id, 'delete');
+ RETURN NULL;
+END;
+\$dhcp6_client_class_ADEL\$
+LANGUAGE plpgsql;
+
+-- Create dhcp6_client_class delete trigger
+CREATE TRIGGER dhcp6_client_class_ADEL
+ AFTER DELETE ON dhcp6_client_class
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_client_class_ADEL();
+
+-- -----------------------------------------------------------------------
+-- Create a table associating client classes stored in the
+-- dhcp6_client_class table with their dependencies. There is
+-- an M:N relationship between these tables. Each class may have
+-- many dependencies (created using member operator in test expression),
+-- and each class may be a dependency for many other classes. A caller
+-- is responsible for inserting dependencies for a class after inserting
+-- or updating it in the dhcp6_client_class table. A caller should
+-- delete all existing dependencies for an updated client class, evaluate
+-- test expression to discover new dependencies (in case test expression
+-- has changed), and insert new dependencies to this table.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_dependency (
+ class_id BIGINT PRIMARY KEY NOT NULL,
+ dependency_id BIGINT NOT NULL,
+
+ CONSTRAINT dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id) ON DELETE CASCADE,
+ CONSTRAINT dhcp6_client_class_dependency_id FOREIGN KEY (dependency_id)
+ REFERENCES dhcp6_client_class (id)
+);
+
+CREATE INDEX dhcp6_client_class_dependency_id_idx on dhcp6_client_class_dependency (dependency_id);
+
+-- -----------------------------------------------------------------------
+-- Stored procedure verifying if class dependency is met. It includes
+-- checking if referenced classes exist, are associated with the same
+-- server or all servers, and are defined before the class specified with
+-- class_id.
+--
+-- Parameters:
+-- - p_class_id id client class,
+-- - p_dependency_id id of the dependency.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION checkDHCPv6ClientClassDependency(p_class_id BIGINT,
+ p_dependency_id BIGINT)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ class_index BIGINT;
+ dependency_index BIGINT;
+BEGIN
+ -- We could check the same with a constraint but later in this
+ -- trigger we use this value to verify if the dependencies are
+ -- met.
+ IF p_class_id IS NULL THEN
+ RAISE EXCEPTION 'Client class id must not be NULL.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ IF p_dependency_id IS NULL THEN
+ RAISE EXCEPTION 'Class dependency id must not be NULL.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Dependencies on self make no sense.
+ IF p_class_id = p_dependency_id THEN
+ RAISE EXCEPTION 'Client class must not have dependency on self.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check position of our class in the hierarchy.
+ SELECT o.order_index INTO class_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = p_class_id;
+
+ IF class_index IS NULL THEN
+ RAISE EXCEPTION 'Client class with id % does not exist.', p_class_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check position of the dependency.
+ SELECT o.order_index INTO dependency_index FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o ON c.id = o.class_id
+ WHERE c.id = p_dependency_id;
+
+ IF dependency_index IS NULL THEN
+ RAISE EXCEPTION 'Dependency class with id % does not exist.', p_dependency_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The dependency must not be later than our class.
+ IF dependency_index > class_index THEN
+ RAISE EXCEPTION
+ 'Client class with id % must not depend on class defined later with id %',
+ p_class_id, p_dependency_id USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- Check if all servers associated with the new class have dependent
+ -- classes configured. This catches the cases that class A belongs to
+ -- server1 and depends on class B which belongs only to server 2.
+ -- It is fine if the class B belongs to all servers in this case.
+ -- Make a SELECT on the dhcp6_client_class_server table to gather
+ -- all servers to which the class belongs. LEFT JOIN it with the
+ -- same table, selecting all records matching the dependency class
+ -- and the servers to which the new class belongs. If there are
+ -- any NULL records joined it implies that some dependencies are
+ -- not met (didn't find a dependency for at least one server).
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_server AS t1
+ LEFT JOIN dhcp6_client_class_server AS t2
+ ON t2.class_id = p_dependency_id AND (t2.server_id = 1 OR t2.server_id = t1.server_id)
+ WHERE t1.class_id = p_class_id AND t2.server_id IS NULL
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unmet dependencies for client class with id %', p_class_id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+ RETURN;
+END;\$\$;
+
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_check_dependency_BINS()
+ RETURNS trigger AS \$dhcp6_client_class_check_dependency_BINS\$
+BEGIN
+ PERFORM checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NULL;
+END;
+\$dhcp6_client_class_check_dependency_BINS\$
+LANGUAGE plpgsql;
+
+-- Create dhcp6_client_class_check_dependency_BINS before insert trigger.
+CREATE TRIGGER dhcp6_client_class_check_dependency_BINS
+ BEFORE INSERT ON dhcp6_client_class_dependency
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_client_class_check_dependency_BINS();
+
+-- -----------------------------------------------------------------------
+-- Stored procedure setting client class indirect dependency on KNOWN or
+-- UNKNOWN built-in classes by checking this flag for the client classes
+-- on which it depends.
+--
+-- Parameters:
+-- - client_class_id id of the client class which dependency is set,
+-- - dependency_id id of the client class on which the given class depends.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION updateDHCPv6ClientClassKnownDependency(client_class_id BIGINT,
+ dependency_id BIGINT)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ dependency BOOLEAN;
+BEGIN
+ -- Check if the dependency class references KNOWN/UNKNOWN.
+ SELECT depend_on_known_directly INTO dependency FROM dhcp6_client_class
+ WHERE id = dependency_id;
+
+ -- If it doesn't, check if the dependency references KNOWN/UNKNOWN
+ -- indirectly (via other classes).
+ IF dependency = false THEN
+ SELECT depend_on_known_indirectly INTO dependency FROM dhcp6_client_class_order
+ WHERE class_id = dependency_id;
+ END IF;
+
+ IF dependency = true THEN
+ UPDATE dhcp6_client_class_order
+ SET depend_on_known_indirectly = true
+ WHERE class_id = client_class_id;
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- -----------------------------------------------------------------------
+-- Trigger setting client class indirect dependency on KNOWN or UNKNOWN
+-- built-in classes by checking this flag for the client classes on which
+-- it depends.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_dependency_AINS()
+ RETURNS TRIGGER AS \$dhcp6_client_class_dependency_AINS\$
+BEGIN
+ PERFORM updateDHCPv6ClientClassKnownDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NULL;
+END;
+\$dhcp6_client_class_dependency_AINS\$
+LANGUAGE plpgsql;
+
+-- Create dhcp6_client_class_check_dependency_AINS after insert trigger.
+CREATE TRIGGER dhcp6_client_class_dependency_AINS
+ AFTER INSERT ON dhcp6_client_class_dependency
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_client_class_dependency_AINS();
+
+-- -----------------------------------------------------------------------
+-- Stored procedure to be executed before committing a transaction
+-- updating a DHCPv6 client class. It verifies if the class dependency on
+-- KNOWN or UNKNOWN built-in classes has changed as a result of the
+-- update. It signals an error if it has changed and there is at least
+-- one class depending on this class.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION checkDHCPv6ClientClassKnownDependencyChange()
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ depended BOOLEAN := false;
+ depends BOOLEAN := false;
+ client_class_id BIGINT;
+ depend_on_known_directly BOOLEAN;
+ depend_on_known_indirectly BOOLEAN;
+BEGIN
+
+ -- Session variables are set upon a client class update.
+ client_class_id := get_session_big_int('kea.client_class_id');
+ IF client_class_id IS NOT NULL THEN
+ -- Check if any of the classes depend on this class. If not,
+ -- it is ok to change the dependency on KNOWN/UNKNOWN.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency
+ WHERE dependency_id = client_class_id LIMIT 1
+ ) THEN
+ -- Using the session variables, determine whether the client class
+ -- depended on KNOWN/UNKNOWN before the update.
+ depend_on_known_directly := get_session_boolean('kea.depend_on_known_directly');
+ depend_on_known_indirectly := get_session_boolean('kea.depend_on_known_indirectly');
+ IF depend_on_known_directly = true OR depend_on_known_indirectly = true THEN
+ SET depended = true;
+ END IF;
+
+ -- Check if the client class depends on KNOWN/UNKNOWN after the update.
+ SELECT depend_on_known_directly INTO depends FROM dhcp6_client_class
+ WHERE id = client_class_id;
+
+ -- If it doesn't depend directly, check indirect dependencies.
+ IF depends = false THEN
+ SELECT depend_on_known_indirectly INTO depends FROM dhcp6_client_class_order
+ WHERE class_id = client_class_id;
+ END IF;
+
+ -- The resulting dependency on KNOWN/UNKNOWN must not change.
+ IF depended <> depends THEN
+ RAISE EXCEPTION 'Class dependency on KNOWN/UNKNOWN built-in classes must not change.'
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+ END IF;
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- -----------------------------------------------------------------------
+-- Create table matching DHCPv6 classes with the servers.
+-- -----------------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS dhcp6_client_class_server (
+ class_id BIGINT NOT NULL,
+ server_id BIGINT NOT NULL,
+ modification_ts TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL,
+ PRIMARY KEY (class_id,server_id),
+ CONSTRAINT fk_dhcp6_client_class_class_id FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE,
+ CONSTRAINT fk_dhcp6_client_class_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id)
+);
+
+CREATE INDEX fk_dhcp6_client_class_server_id ON dhcp6_client_class_server (server_id);
+
+-- Trigger function for dhcp4_pool_BDEL called BEFORE DELETE on dhcp4_pool
+-- It removes pool specific options upon removal of the pool.
+CREATE OR REPLACE FUNCTION func_dhcp4_pool_BDEL() RETURNS TRIGGER AS \$dhcp4_pool_BDEL\$
+BEGIN
+ DELETE FROM dhcp4_options WHERE scope_id = 5 AND pool_id = OLD.id;
+ RETURN OLD;
+END;
+\$dhcp4_pool_BDEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_pool_BDEL
+ BEFORE DELETE ON dhcp4_pool
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_pool_BDEL();
+
+-- Trigger function for dhcp6_pool_BDEL called BEFORE DELETE on dhcp6_pool
+-- It removes pool specific options upon removal of the pool.
+CREATE OR REPLACE FUNCTION func_dhcp6_pool_BDEL() RETURNS TRIGGER AS \$dhcp6_pool_BDEL\$
+BEGIN
+ DELETE FROM dhcp6_options WHERE scope_id = 5 AND pool_id = OLD.id;
+ RETURN OLD;
+END;
+\$dhcp6_pool_BDEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_pool_BDEL
+ BEFORE DELETE ON dhcp6_pool
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_pool_BDEL();
+
+-- Trigger function for dhcp4_global_parameter_AINS called AFTER INSERT on dhcp4_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp4_global_parameter_AINS() RETURNS TRIGGER AS \$dhcp4_global_parameter_AINS\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_global_parameter', NEW.id, 'create');
+ RETURN NULL;
+END;
+\$dhcp4_global_parameter_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_global_parameter_AINS
+ AFTER INSERT ON dhcp4_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_global_parameter_AINS();
+
+
+-- Trigger function for dhcp4_global_parameter_AUPD called AFTER UPDATE on dhcp4_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp4_global_parameter_AUPD() RETURNS TRIGGER AS \$dhcp4_global_parameter_AUPD\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_global_parameter', NEW.id, 'update');
+ RETURN NULL;
+END;
+\$dhcp4_global_parameter_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_global_parameter_AUPD
+ AFTER UPDATE ON dhcp4_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_global_parameter_AUPD();
+
+
+-- Trigger function for dhcp4_global_parameter_ADEL called AFTER DELETE on dhcp4_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp4_global_parameter_ADEL() RETURNS TRIGGER AS \$dhcp4_global_parameter_ADEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_global_parameter', OLD.id, 'delete');
+ RETURN NULL;
+END;
+\$dhcp4_global_parameter_ADEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_global_parameter_ADEL
+ AFTER DELETE ON dhcp4_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_global_parameter_ADEL();
+
+-- Trigger function for dhcp4_subnet_AINS called AFTER INSERT on dhcp4_subnet
+CREATE OR REPLACE FUNCTION func_dhcp4_subnet_AINS() RETURNS TRIGGER AS \$dhcp4_subnet_AINS\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_subnet', NEW.subnet_id, 'create');
+ RETURN NULL;
+END;
+\$dhcp4_subnet_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_subnet_AINS
+ AFTER INSERT ON dhcp4_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_subnet_AINS();
+
+
+-- Trigger function for dhcp4_subnet_AUPD called AFTER UPDATE on dhcp4_subnet
+CREATE OR REPLACE FUNCTION func_dhcp4_subnet_AUPD() RETURNS TRIGGER AS \$dhcp4_subnet_AUPD\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_subnet', NEW.subnet_id, 'update');
+ RETURN NULL;
+END;
+\$dhcp4_subnet_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_subnet_AUPD
+ AFTER UPDATE ON dhcp4_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_subnet_AUPD();
+
+
+-- Trigger function for dhcp4_shared_network_AINS called AFTER INSERT on dhcp4_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp4_shared_network_AINS() RETURNS TRIGGER AS \$dhcp4_shared_network_AINS\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_shared_network', NEW.id, 'create');
+ RETURN NULL;
+END;
+\$dhcp4_shared_network_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_shared_network_AINS
+ AFTER INSERT ON dhcp4_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_shared_network_AINS();
+
+-- Trigger function for dhcp4_shared_network_AUPD called AFTER UPDATE on dhcp4_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp4_shared_network_AUPD() RETURNS TRIGGER AS \$dhcp4_shared_network_AUPD\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_shared_network', NEW.id, 'update');
+ RETURN NULL;
+END;
+\$dhcp4_shared_network_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_shared_network_AUPD
+ AFTER UPDATE ON dhcp4_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_shared_network_AUPD();
+
+-- Trigger function for dhcp4_shared_network_ADEL called AFTER DELETE on dhcp4_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp4_shared_network_ADEL() RETURNS TRIGGER AS \$dhcp4_shared_network_ADEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, 'delete');
+ RETURN NULL;
+END;
+\$dhcp4_shared_network_ADEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_shared_network_ADEL
+ AFTER DELETE ON dhcp4_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_shared_network_ADEL();
+
+-- Trigger function for dhcp4_option_def_AINS called AFTER INSERT on dhcp4_option_def
+CREATE OR REPLACE FUNCTION func_dhcp4_option_def_AINS() RETURNS TRIGGER AS \$dhcp4_option_def_AINS\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_option_def', NEW.id, 'create');
+ RETURN NULL;
+END;
+\$dhcp4_option_def_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_option_def_AINS
+ AFTER INSERT ON dhcp4_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_option_def_AINS();
+
+-- Trigger function for dhcp4_option_def_AUPD called AFTER UPDATE on dhcp4_option_def
+CREATE OR REPLACE FUNCTION func_dhcp4_option_def_AUPD() RETURNS TRIGGER AS \$dhcp4_option_def_AUPD\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_option_def', NEW.id, 'update');
+ RETURN NULL;
+END;
+\$dhcp4_option_def_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_option_def_AUPD
+ AFTER UPDATE ON dhcp4_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_option_def_AUPD();
+
+-- Trigger function for dhcp4_option_def_ADEL called AFTER DELETE on dhcp4_option_def
+CREATE OR REPLACE FUNCTION func_dhcp4_option_def_ADEL() RETURNS TRIGGER AS \$dhcp4_option_def_ADEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_option_def', OLD.id, 'delete');
+ RETURN NULL;
+END;
+\$dhcp4_option_def_ADEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_option_def_ADEL
+ AFTER DELETE ON dhcp4_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_option_def_ADEL();
+
+-- -----------------------------------------------------
+--
+-- Stored procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: 'create', 'update' or 'delete'
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc.
+-- - option_id: identifier of the option.
+-- - p_subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - p_modification_ts: modification timestamp of the
+-- option.
+-- Some arguments are prefixed with "p_" to avoid ambiguity
+-- with column names in SQL statements. PostgreSQL does not
+-- allow table aliases to be used with column names in update
+-- set expressions.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP4(modification_type VARCHAR,
+ scope_id SMALLINT,
+ option_id INT,
+ p_subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR,
+ pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid VARCHAR(128);
+ sid BIGINT;
+ cascade_transaction BOOLEAN := true;
+ ct TEXT;
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp4_options' is when a global option is added.
+ -- Global options do not have the owner.
+
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp4_options' table.
+ PERFORM createAuditEntryDHCP4('dhcp4_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, update
+ -- the modification timestamp of this subnet to allow the
+ -- servers to refresh the subnet information. This will
+ -- also result in creating an audit entry for this subnet.
+ UPDATE dhcp4_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = p_subnet_id;
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- update the modification timestamp of this shared network
+ -- to allow the servers to refresh the shared network
+ -- information. This will also result in creating an
+ -- audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp4_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp4_shared_network SET modification_ts = p_modification_ts
+ WHERE id = snid;
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, update
+ -- the modification timestamp of the owning subnet.
+ SELECT dhcp4_pool.subnet_id INTO sid FROM dhcp4_pool WHERE id = pool_id;
+ UPDATE dhcp4_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ END IF;
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- Trigger function for dhcp4_options_AINS called AFTER INSERT on dhcp4_options
+CREATE OR REPLACE FUNCTION func_dhcp4_options_AINS() RETURNS TRIGGER AS \$dhcp4_options_AINS\$
+BEGIN
+ PERFORM createOptionAuditDHCP4('create', NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.modification_ts);
+ RETURN NULL;
+END;
+\$dhcp4_options_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_options_AINS
+ AFTER INSERT ON dhcp4_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_options_AINS();
+
+
+-- Trigger function for dhcp4_options_AUPD called AFTER UPDATE on dhcp4_options
+CREATE OR REPLACE FUNCTION func_dhcp4_options_AUPD() RETURNS TRIGGER AS \$dhcp4_options_AUPD\$
+BEGIN
+ PERFORM createOptionAuditDHCP4('update', NEW.scope_id, NEW.option_id, NEW.dhcp4_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.modification_ts);
+ RETURN NULL;
+END;
+\$dhcp4_options_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_options_AUPD
+ AFTER UPDATE ON dhcp4_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_options_AUPD();
+
+-- Trigger function for dhcp4_options_ADEL called AFTER DELETE on dhcp4_options
+CREATE OR REPLACE FUNCTION func_dhcp4_options_ADEL() RETURNS TRIGGER AS \$dhcp4_options_ADEL\$
+BEGIN
+ PERFORM createOptionAuditDHCP4('delete', OLD.scope_id, OLD.option_id, OLD.dhcp4_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id,
+ NOW());
+ RETURN NULL;
+END;
+\$dhcp4_options_ADEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_options_ADEL
+ AFTER DELETE ON dhcp4_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_options_ADEL();
+
+-- Trigger function for dhcp6_global_parameter_AINS called AFTER INSERT on dhcp6_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp6_global_parameter_AINS() RETURNS TRIGGER AS \$dhcp6_global_parameter_AINS\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_global_parameter', NEW.id, 'create');
+ RETURN NULL;
+END;
+\$dhcp6_global_parameter_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_global_parameter_AINS
+ AFTER INSERT ON dhcp6_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_global_parameter_AINS();
+
+-- Trigger function for dhcp6_global_parameter_AUPD called AFTER UPDATE on dhcp6_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp6_global_parameter_AUPD() RETURNS TRIGGER AS \$dhcp6_global_parameter_AUPD\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_global_parameter', NEW.id, 'update');
+ RETURN NULL;
+END;
+\$dhcp6_global_parameter_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_global_parameter_AUPD
+ AFTER UPDATE ON dhcp6_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_global_parameter_AUPD();
+
+-- Trigger function for dhcp6_global_parameter_ADEL called AFTER DELETE on dhcp6_global_parameter
+CREATE OR REPLACE FUNCTION func_dhcp6_global_parameter_ADEL() RETURNS TRIGGER AS \$dhcp6_global_parameter_ADEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_global_parameter', OLD.id, 'delete');
+ RETURN NULL;
+END;
+\$dhcp6_global_parameter_ADEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_global_parameter_ADEL
+ AFTER DELETE ON dhcp6_global_parameter
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_global_parameter_ADEL();
+
+-- Trigger function for dhcp6_subnet_AINS called AFTER INSERT on dhcp6_subnet
+CREATE OR REPLACE FUNCTION func_dhcp6_subnet_AINS() RETURNS TRIGGER AS \$dhcp6_subnet_AINS\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', NEW.subnet_id, 'create');
+ RETURN NULL;
+END;
+\$dhcp6_subnet_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_subnet_AINS
+ AFTER INSERT ON dhcp6_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_subnet_AINS();
+
+-- Trigger function for dhcp6_subnet_AUPD called AFTER UPDATE on dhcp6_subnet
+CREATE OR REPLACE FUNCTION func_dhcp6_subnet_AUPD() RETURNS TRIGGER AS \$dhcp6_subnet_AUPD\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', NEW.subnet_id, 'update');
+ RETURN NULL;
+END;
+\$dhcp6_subnet_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_subnet_AUPD
+ AFTER UPDATE ON dhcp6_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_subnet_AUPD();
+
+-- Trigger function for dhcp6_shared_network_AINS called AFTER INSERT on dhcp6_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp6_shared_network_AINS() RETURNS TRIGGER AS \$dhcp6_shared_network_AINS\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', NEW.id, 'create');
+ RETURN NULL;
+END;
+\$dhcp6_shared_network_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_shared_network_AINS
+ AFTER INSERT ON dhcp6_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_shared_network_AINS();
+
+-- Trigger function for dhcp6_shared_network_AUPD called AFTER UPDATE on dhcp6_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp6_shared_network_AUPD() RETURNS TRIGGER AS \$dhcp6_shared_network_AUPD\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', NEW.id, 'update');
+ RETURN NULL;
+END;
+\$dhcp6_shared_network_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_shared_network_AUPD
+ AFTER UPDATE ON dhcp6_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_shared_network_AUPD();
+
+-- Trigger function for dhcp6_shared_network_ADEL called AFTER DELETE on dhcp6_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp6_shared_network_ADEL() RETURNS TRIGGER AS \$dhcp6_shared_network_ADEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, 'delete');
+ RETURN NULL;
+END;
+\$dhcp6_shared_network_ADEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_shared_network_ADEL
+ AFTER DELETE ON dhcp6_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_shared_network_ADEL();
+
+-- Trigger function for dhcp6_option_def_AINS called AFTER INSERT on dhcp6_option_def
+CREATE OR REPLACE FUNCTION func_dhcp6_option_def_AINS() RETURNS TRIGGER AS \$dhcp6_option_def_AINS\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_option_def', NEW.id, 'create');
+ RETURN NULL;
+END;
+\$dhcp6_option_def_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_option_def_AINS
+ AFTER INSERT ON dhcp6_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_option_def_AINS();
+
+-- Trigger function for dhcp6_option_def_AUPD called AFTER UPDATE on dhcp6_option_def
+CREATE OR REPLACE FUNCTION func_dhcp6_option_def_AUPD() RETURNS TRIGGER AS \$dhcp6_option_def_AUPD\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_option_def', NEW.id, 'update');
+ RETURN NULL;
+END;
+\$dhcp6_option_def_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_option_def_AUPD
+ AFTER UPDATE ON dhcp6_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_option_def_AUPD();
+
+-- Trigger function for dhcp6_option_def_ADEL called AFTER DELETE on dhcp6_option_def
+CREATE OR REPLACE FUNCTION func_dhcp6_option_def_ADEL() RETURNS TRIGGER AS \$dhcp6_option_def_ADEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_option_def', OLD.id, 'delete');
+ RETURN NULL;
+END;
+\$dhcp6_option_def_ADEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_option_def_ADEL
+ AFTER DELETE ON dhcp6_option_def
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_option_def_ADEL();
+
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- - modification_ts: modification timestamp of the
+-- option.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP6(modification_type VARCHAR(32),
+ scope_id SMALLINT,
+ option_id INT,
+ subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR(128),
+ pool_id BIGINT,
+ pd_pool_id BIGINT,
+ modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid VARCHAR(128);
+ sid BIGINT;
+ cascade_transaction BOOLEAN := false;
+
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp6_options' is when a global option is added.
+ -- Global options do not have the owner.
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp6_options' table.
+ PERFORM createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, create
+ -- audit entry for the entire subnet, which indicates that
+ -- it should be treated as the subnet update.
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', subnet_id, 'update');
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- create audit entry for the shared network which
+ -- indicates that it should be treated as the shared
+ -- network update.
+ SELECT id INTO snid FROM dhcp6_shared_network
+ WHERE name = network_name LIMIT 1;
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', snid, 'update');
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, create
+ -- audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', sid, 'update');
+ ELSEIF scope_id = 6 THEN
+ -- If pd pool specific option is added or modified, create
+ -- audit entry for the subnet which this pd pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool
+ WHERE id = pd_pool_id;
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', sid, 'update');
+ END IF;
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- Trigger function for dhcp6_options_AINS called AFTER INSERT on dhcp6_options
+CREATE OR REPLACE FUNCTION func_dhcp6_options_AINS() RETURNS TRIGGER AS \$dhcp6_options_AINS\$
+BEGIN
+ PERFORM createOptionAuditDHCP6('create', NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.pd_pool_id, NEW.modification_ts);
+ RETURN NULL;
+END;
+\$dhcp6_options_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_options_AINS
+ AFTER INSERT ON dhcp6_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_options_AINS();
+
+-- Trigger function for dhcp6_options_AUPD called AFTER UPDATE on dhcp6_options
+CREATE OR REPLACE FUNCTION func_dhcp6_options_AUPD() RETURNS TRIGGER AS \$dhcp6_options_AUPD\$
+BEGIN
+ PERFORM createOptionAuditDHCP6('update', NEW.scope_id, NEW.option_id, NEW.dhcp6_subnet_id,
+ NEW.host_id, NEW.shared_network_name, NEW.pool_id,
+ NEW.pd_pool_id, NEW.modification_ts);
+ RETURN NULL;
+END;
+\$dhcp6_options_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_options_AUPD
+ AFTER UPDATE ON dhcp6_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_options_AUPD();
+
+
+-- Trigger function for dhcp6_options_ADEL called AFTER DELETE on dhcp6_options
+CREATE OR REPLACE FUNCTION func_dhcp6_options_ADEL() RETURNS TRIGGER AS \$dhcp6_options_ADEL\$
+BEGIN
+ PERFORM createOptionAuditDHCP6('delete', OLD.scope_id, OLD.option_id, OLD.dhcp6_subnet_id,
+ OLD.host_id, OLD.shared_network_name, OLD.pool_id,
+ OLD.pd_pool_id, NOW());
+ RETURN NULL;
+END;
+\$dhcp6_options_ADEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_options_ADEL
+ AFTER DELETE ON dhcp6_options
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_options_ADEL();
+
+
+-- Trigger function for dhcp4_server_AINS called AFTER INSERT on dhcp4_server
+CREATE OR REPLACE FUNCTION func_dhcp4_server_AINS() RETURNS TRIGGER AS \$dhcp4_server_AINS\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_server', NEW.id, 'create');
+ RETURN NULL;
+END;
+\$dhcp4_server_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_server_AINS
+ AFTER INSERT ON dhcp4_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_server_AINS();
+
+
+-- Trigger function for dhcp4_server_AUPD called AFTER UPDATE on dhcp4_server
+CREATE OR REPLACE FUNCTION func_dhcp4_server_AUPD() RETURNS TRIGGER AS \$dhcp4_server_AUPD\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_server', NEW.id, 'update');
+ RETURN NULL;
+END;
+\$dhcp4_server_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_server_AUPD
+ AFTER UPDATE ON dhcp4_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_server_AUPD();
+
+
+-- Trigger function for dhcp4_server_ADEL called AFTER DELETE on dhcp4_server
+CREATE OR REPLACE FUNCTION func_dhcp4_server_ADEL() RETURNS TRIGGER AS \$dhcp4_server_ADEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_server', OLD.id, 'delete');
+ RETURN NULL;
+END;
+\$dhcp4_server_ADEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_server_ADEL
+ AFTER DELETE ON dhcp4_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_server_ADEL();
+
+
+-- Trigger function for dhcp6_server_AINS called AFTER INSERT on dhcp6_server
+CREATE OR REPLACE FUNCTION func_dhcp6_server_AINS() RETURNS TRIGGER AS \$dhcp6_server_AINS\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_server', NEW.id, 'create');
+ RETURN NULL;
+END;
+\$dhcp6_server_AINS\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_server_AINS
+ AFTER INSERT ON dhcp6_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_server_AINS();
+
+
+-- Trigger function for dhcp6_server_AUPD called AFTER UPDATE on dhcp6_server
+CREATE OR REPLACE FUNCTION func_dhcp6_server_AUPD() RETURNS TRIGGER AS \$dhcp6_server_AUPD\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_server', NEW.id, 'update');
+ RETURN NULL;
+END;
+\$dhcp6_server_AUPD\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_server_AUPD
+ AFTER UPDATE ON dhcp6_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_server_AUPD();
+
+
+-- Trigger function for dhcp6_server_ADEL called AFTER DELETE on dhcp6_server
+CREATE OR REPLACE FUNCTION func_dhcp6_server_ADEL() RETURNS TRIGGER AS \$dhcp6_server_ADEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_server', OLD.id, 'delete');
+ RETURN NULL;
+END;
+\$dhcp6_server_ADEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_server_ADEL
+ AFTER DELETE ON dhcp6_server
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_server_ADEL();
+
+
+-- Trigger function for dhcp4_shared_network_BDEL called BEFORE DELETE on dhcp4_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp4_shared_network_BDEL() RETURNS TRIGGER AS \$dhcp4_shared_network_BDEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, 'delete');
+ DELETE FROM dhcp4_options WHERE shared_network_name = OLD.name;
+ RETURN OLD;
+END;
+\$dhcp4_shared_network_BDEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_shared_network_BDEL
+ BEFORE DELETE ON dhcp4_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_shared_network_BDEL();
+
+-- Trigger function for dhcp4_subnet_BDEL called BEFORE DELETE on dhcp4_subnet
+CREATE OR REPLACE FUNCTION func_dhcp4_subnet_BDEL() RETURNS TRIGGER AS \$dhcp4_subnet_BDEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_subnet', OLD.subnet_id, 'delete');
+ DELETE FROM dhcp4_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp4_options WHERE dhcp4_subnet_id = OLD.subnet_id;
+ RETURN OLD;
+END;
+\$dhcp4_subnet_BDEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp4_subnet_BDEL
+ BEFORE DELETE ON dhcp4_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp4_subnet_BDEL();
+
+-- Trigger function for dhcp6_shared_network_BDEL called BEFORE DELETE on dhcp6_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp6_shared_network_BDEL() RETURNS TRIGGER AS \$dhcp6_shared_network_BDEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, 'delete');
+ DELETE FROM dhcp6_options WHERE shared_network_name = OLD.name;
+ RETURN OLD;
+END;
+\$dhcp6_shared_network_BDEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_shared_network_BDEL
+ BEFORE DELETE ON dhcp6_shared_network
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_shared_network_BDEL();
+
+-- Trigger function for dhcp6_subnet_BDEL called BEFORE DELETE on dhcp6_subnet
+CREATE OR REPLACE FUNCTION func_dhcp6_subnet_BDEL() RETURNS TRIGGER AS \$dhcp6_subnet_BDEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_subnet', OLD.subnet_id, 'delete');
+ DELETE FROM dhcp6_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp6_pd_pool WHERE subnet_id = OLD.subnet_id;
+ DELETE FROM dhcp6_options WHERE dhcp6_subnet_id = OLD.subnet_id;
+ RETURN OLD;
+END;
+\$dhcp6_subnet_BDEL\$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER dhcp6_subnet_BDEL
+ BEFORE DELETE ON dhcp6_subnet
+ FOR EACH ROW EXECUTE PROCEDURE func_dhcp6_subnet_BDEL();
+
+-- Trigger function for dhcp6_pd_pool_BDEL called BEFORE DELETE on dhcp6_pd_pool
+CREATE OR REPLACE FUNCTION func_dhcp6_pd_pool_BDEL() RETURNS TRIGGER AS \$dhcp6_pd_pool_BDEL\$
+BEGIN
+ DELETE FROM dhcp6_options WHERE scope_id = 6 AND pd_pool_id = OLD.id;
+ RETURN OLD;
+END;
+\$dhcp6_pd_pool_BDEL\$
+LANGUAGE plpgsql;
+
+-- Update the schema version number
+UPDATE schema_version
+ SET version = '7', minor = '0';
+
+-- Schema 7.0 specification ends here.
+
+-- Commit the script transaction
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_007_to_008.sh.in b/src/share/database/scripts/pgsql/upgrade_007_to_008.sh.in
new file mode 100644
index 0000000..fe18652
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_007_to_008.sh.in
@@ -0,0 +1,514 @@
+#!/bin/sh
+
+# Copyright (C) 2021-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "7.0" ]; then
+ printf 'This script upgrades 7.0 to 8.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+START TRANSACTION;
+
+-- This starts schema update to 8.0. It adds a few missing elements for CB and
+-- functions for kea-admin's lease-dump and lease-upload commands.
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv4 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp4_option_def
+ ADD COLUMN class_id BIGINT NULL DEFAULT NULL;
+
+ALTER TABLE dhcp4_option_def
+ ADD CONSTRAINT fk_dhcp4_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp4_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+-- -----------------------------------------------------------------------
+-- Extend the table holding DHCPv6 option definitions with a nullable
+-- column matching option defintions with client classes.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp6_option_def
+ ADD COLUMN class_id BIGINT NULL DEFAULT NULL;
+
+ALTER TABLE dhcp6_option_def
+ ADD CONSTRAINT fk_dhcp6_option_def_client_class_id
+ FOREIGN KEY (class_id)
+ REFERENCES dhcp6_client_class (id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE;
+
+-- -----------------------------------------------------------------------
+-- Add missing preferred_lifetime columns to dhcp6_client_class table.
+-- -----------------------------------------------------------------------
+ALTER TABLE dhcp6_client_class
+ ADD COLUMN preferred_lifetime BIGINT DEFAULT NULL,
+ ADD COLUMN min_preferred_lifetime BIGINT DEFAULT NULL,
+ ADD COLUMN max_preferred_lifetime BIGINT DEFAULT NULL;
+
+-- -----------------------------------------------------------------------
+-- Add option scopes
+-- -----------------------------------------------------------------------
+-- Add scope for shared network specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(4, 'shared-network');
+
+-- Add scope for pool specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(5, 'pool');
+
+-- Add scope for PD pool specific options.
+INSERT INTO dhcp_option_scope (scope_id, scope_name)
+ VALUES(6, 'pd-pool');
+
+
+-- Drop the existing function, createOptionAuditDHCP6 so we can replace it
+-- with one that has slightly different arguments.
+DROP FUNCTION IF EXISTS createOptionAuditDHCP6(modification_type VARCHAR(32),
+ scope_id SMALLINT, option_id INT, subnet_id BIGINT,
+ host_id INT, network_name VARCHAR(128),
+ pool_id BIGINT, pd_pool_id BIGINT,
+ modification_ts TIMESTAMP WITH TIME ZONE);
+
+-- -----------------------------------------------------
+--
+-- New version of the createOptionAuditDHCP6 stored
+-- procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- - modification_ts: modification timestamp of the
+-- option.
+-- Some arguments are prefixed with "p_" to avoid ambiguity
+-- with column names in SQL statements. PostgreSQL does not
+-- allow table aliases to be used with column names in update
+-- set expressions.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP6(modification_type VARCHAR(32),
+ scope_id SMALLINT,
+ option_id INT,
+ p_subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR(128),
+ pool_id BIGINT,
+ pd_pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid VARCHAR(128);
+ sid BIGINT;
+ cascade_transaction BOOLEAN := false;
+
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp6_options' is when a global option is added.
+ -- Global options do not have the owner.
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp6_options' table.
+ PERFORM createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, update
+ -- the modification timestamp of this subnet to allow the
+ -- servers to refresh the subnet information. This will
+ -- also result in creating an audit entry for this subnet.
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = p_subnet_id;
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- update the modification timestamp of this shared network
+ -- to allow the servers to refresh the shared network
+ -- information. This will also result in creating an
+ -- audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp6_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp6_shared_network SET modification_ts = p_modification_ts
+ WHERE id = snid;
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, update
+ -- the modification timestamp of the owning subnet.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ ELSEIF scope_id = 6 THEN
+ -- If pd pool specific option is added or modified, create
+ -- audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ END IF;
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- Fix mangled constraints on dhcp4_subnet_server table.
+ALTER TABLE dhcp4_subnet_server
+ DROP CONSTRAINT fk_dhcp6_subnet_server_server_id,
+ ADD CONSTRAINT fk_dhcp4_subnet_server_server_id
+ FOREIGN KEY (server_id) REFERENCES dhcp4_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
+ DROP CONSTRAINT fk_dhcp6_subnet_server_subnet_id,
+ ADD CONSTRAINT fk_dhcp4_subnet_server_subnet_id
+ FOREIGN KEY (subnet_id) REFERENCES dhcp4_subnet (subnet_id) ON DELETE CASCADE ON UPDATE NO ACTION;
+
+-- Add missing foreign key indexes. PostgreSQL does not automatically create indexes for
+-- foreign key constraints. These have been added using the basic guideline:
+--
+-- If the constraint does not reference a static table (e.g. parameter_data_type),
+-- and the referencing column is not the primary key or the first
+-- column in the primary key, and does not already have an index, then an index
+-- should be added to the table for the referencing column.
+--
+-- dhcp6_global_parameter_server
+CREATE INDEX fk_dhcp6_global_parameter_server_server_id ON dhcp6_global_parameter_server (server_id);
+
+-- dhcp6_options
+-- Missing foreign key constraint and indexes
+ALTER TABLE dhcp6_options ADD CONSTRAINT fk_dhcp6_options_subnet
+ FOREIGN KEY (dhcp6_subnet_id)
+ REFERENCES dhcp6_subnet(subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+CREATE INDEX fk_dhcp6_options_subnet ON dhcp6_options (dhcp6_subnet_id);
+CREATE INDEX fk_dhcp6_options_pd_pool ON dhcp6_options (pd_pool_id);
+CREATE INDEX fk_dhcp6_options_pool ON dhcp6_options (pool_id);
+CREATE INDEX fk_dhcp6_options_shared_network ON dhcp6_options (shared_network_name);
+
+-- dhcp6_option_def_server
+-- Missing foreign key constraints and index
+ALTER TABLE dhcp6_option_def_server
+ ADD CONSTRAINT fk_dhcp6_option_def_server_option_def_id FOREIGN KEY (option_def_id)
+ REFERENCES dhcp6_option_def (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ ADD CONSTRAINT fk_dhcp6_option_def_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp6_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+CREATE INDEX fk_dhcp6_option_def_server_server_id ON dhcp6_option_def_server (server_id);
+
+-- dhcp6_option_def
+CREATE INDEX fk_dhcp6_option_def_client_class_id ON dhcp6_option_def (class_id);
+
+-- dhcp4_global_parameter_server
+CREATE INDEX fk_dhcp4_global_parameter_server_server_id ON dhcp4_global_parameter_server (server_id);
+
+-- dhcp4_options
+-- Missing foreign key constraint and indexes
+ALTER TABLE dhcp4_options ADD CONSTRAINT fk_dhcp4_options_subnet
+ FOREIGN KEY (dhcp4_subnet_id)
+ REFERENCES dhcp4_subnet(subnet_id)
+ ON DELETE CASCADE ON UPDATE CASCADE;
+
+CREATE INDEX fk_dhcp4_options_subnet ON dhcp4_options (dhcp4_subnet_id);
+CREATE INDEX fk_dhcp4_options_pool ON dhcp4_options (pool_id);
+CREATE INDEX fk_dhcp4_options_shared_network ON dhcp4_options (shared_network_name);
+
+-- dhcp4_option_def_server
+-- Missing foreign key constraints and index
+ALTER TABLE dhcp4_option_def_server
+ ADD CONSTRAINT fk_dhcp4_option_def_server_option_def_id FOREIGN KEY (option_def_id)
+ REFERENCES dhcp4_option_def (id) ON DELETE CASCADE ON UPDATE NO ACTION,
+ ADD CONSTRAINT fk_dhcp4_option_def_server_server_id FOREIGN KEY (server_id)
+ REFERENCES dhcp4_server (id) ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+CREATE INDEX fk_dhcp4_option_def_server_server_id ON dhcp4_option_def_server (server_id);
+
+-- dhcp4_option_def
+CREATE INDEX fk_dhcp4_option_def_client_class_id ON dhcp4_option_def (class_id);
+
+-- Create a function that separates groups of two hexadecimals
+-- with colons.
+CREATE OR REPLACE FUNCTION colonSeparatedHex(hex TEXT)
+RETURNS TEXT
+AS \$\$
+DECLARE
+ i INT := 3;
+ length INT := LENGTH(hex);
+ output TEXT;
+BEGIN
+ -- Add a leading zero if the first octet has a single hexadecimal character.
+ IF MOD(length, 2) = 1 THEN
+ hex := CONCAT('0', hex);
+ length := length + 1;
+ END IF;
+
+ -- Start with the first octet.
+ output := SUBSTR(hex, 1, 2);
+
+ -- Add one octet at a time and a leading colon with each.
+ WHILE i < length LOOP
+ output := CONCAT(output, ':', SUBSTR(hex, i, 2));
+ i := i + 2;
+ END LOOP;
+
+ -- Memfile uses lowercase hexadecimals.
+ output := LOWER(output);
+
+ RETURN output;
+END
+\$\$ LANGUAGE plpgsql;
+
+-- Modify the function to output a memfile-ready CSV file.
+-- Some columns that are SMALLINT in the lease4 table have their type promoted
+-- to INT in the declaration of this function for backwards compatibility with
+-- PostgreSQL versions.
+DROP FUNCTION IF EXISTS lease4DumpData();
+CREATE OR REPLACE FUNCTION lease4DumpData()
+RETURNS TABLE (
+ address INET,
+ hwaddr VARCHAR,
+ client_id VARCHAR,
+ valid_lifetime BIGINT,
+ expire BIGINT,
+ subnet_id BIGINT,
+ fqdn_fwd INT,
+ fqdn_rev INT,
+ hostname VARCHAR,
+ state INT8,
+ user_context VARCHAR
+) AS \$\$
+ SELECT
+ ('0.0.0.0'::inet + address),
+ colonSeparatedHex(encode(hwaddr, 'hex')),
+ colonSeparatedHex(encode(client_id, 'hex')),
+ valid_lifetime,
+ extract(epoch from expire)::bigint,
+ subnet_id,
+ fqdn_fwd::int,
+ fqdn_rev::int,
+ replace(hostname, ',', '&#x2c'),
+ state,
+ replace(user_context, ',', '&#x2c')
+ FROM lease4
+ ORDER BY address;
+\$\$ LANGUAGE SQL;
+
+-- hwtype and hwaddr_source need to be last to match memfile format.
+DROP FUNCTION IF EXISTS lease6DumpHeader();
+CREATE OR REPLACE FUNCTION lease6DumpHeader()
+RETURNS TEXT AS \$\$
+ SELECT CAST('address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state,user_context,hwtype,hwaddr_source' AS TEXT) AS result;
+\$\$ LANGUAGE SQL;
+
+-- Modify the function to output a memfile-ready CSV file.
+-- Some columns that are SMALLINT in the lease6 table have their type promoted
+-- to INT in the declaration of this function for backwards compatibility with
+-- PostgreSQL versions.
+DROP FUNCTION IF EXISTS lease6DumpData();
+CREATE OR REPLACE FUNCTION lease6DumpData()
+RETURNS TABLE (
+ address VARCHAR,
+ duid VARCHAR,
+ valid_lifetime BIGINT,
+ expire BIGINT,
+ subnet_id BIGINT,
+ pref_lifetime BIGINT,
+ lease_type SMALLINT,
+ iaid INT,
+ prefix_len SMALLINT,
+ fqdn_fwd INT,
+ fqdn_rev INT,
+ hostname VARCHAR,
+ hwaddr VARCHAR,
+ state INT8,
+ user_context VARCHAR,
+ hwtype SMALLINT,
+ hwaddr_source SMALLINT
+) AS \$\$
+ SELECT
+ address,
+ colonSeparatedHex(encode(duid, 'hex')),
+ valid_lifetime,
+ extract(epoch from expire)::bigint,
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd::int,
+ fqdn_rev::int,
+ replace(hostname, ',', '&#x2c'),
+ colonSeparatedHex(encode(hwaddr, 'hex')),
+ state,
+ replace(user_context, ',', '&#x2c'),
+ hwtype,
+ hwaddr_source
+ FROM lease6
+ ORDER BY address;
+\$\$ LANGUAGE SQL;
+
+-- Create a procedure that inserts a v4 lease from memfile data.
+-- Some columns that are SMALLINT in the lease4 table have their type promoted
+-- to INT in the declaration of this function for backwards compatibility with
+-- PostgreSQL versions.
+CREATE OR REPLACE FUNCTION lease4Upload(
+ IN address VARCHAR,
+ IN hwaddr VARCHAR,
+ IN client_id VARCHAR,
+ IN valid_lifetime BIGINT,
+ IN expire BIGINT,
+ IN subnet_id BIGINT,
+ IN fqdn_fwd INT,
+ IN fqdn_rev INT,
+ IN hostname VARCHAR,
+ IN state INT8,
+ IN user_context VARCHAR
+) RETURNS VOID AS \$\$
+BEGIN
+ INSERT INTO lease4 (
+ address,
+ hwaddr,
+ client_id,
+ valid_lifetime,
+ expire,
+ subnet_id,
+ fqdn_fwd,
+ fqdn_rev,
+ hostname,
+ state,
+ user_context
+ ) VALUES (
+ address::inet - '0.0.0.0'::inet,
+ decode(replace(hwaddr, ':', ''), 'hex'),
+ decode(replace(client_id, ':', ''), 'hex'),
+ valid_lifetime,
+ to_timestamp(expire),
+ subnet_id,
+ fqdn_fwd::int::boolean,
+ fqdn_rev::int::boolean,
+ replace(hostname, '&#x2c', ','),
+ state,
+ replace(user_context, '&#x2c', ',')
+ );
+END
+\$\$ LANGUAGE plpgsql;
+
+-- Create a procedure that inserts a v6 lease from memfile data.
+-- Some columns that are SMALLINT in the lease6 table have their type promoted
+-- to INT in the declaration of this function for backwards compatibility with
+-- PostgreSQL versions.
+CREATE OR REPLACE FUNCTION lease6Upload(
+ IN address VARCHAR,
+ IN duid VARCHAR,
+ IN valid_lifetime BIGINT,
+ IN expire BIGINT,
+ IN subnet_id BIGINT,
+ IN pref_lifetime BIGINT,
+ IN lease_type INT,
+ IN iaid INT,
+ IN prefix_len INT,
+ IN fqdn_fwd INT,
+ IN fqdn_rev INT,
+ IN hostname VARCHAR,
+ IN hwaddr VARCHAR,
+ IN state INT8,
+ IN user_context VARCHAR,
+ IN hwtype INT,
+ IN hwaddr_source INT
+) RETURNS VOID AS \$\$
+BEGIN
+ INSERT INTO lease6 (
+ address,
+ duid,
+ valid_lifetime,
+ expire,
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd,
+ fqdn_rev,
+ hostname,
+ hwaddr,
+ state,
+ user_context,
+ hwtype,
+ hwaddr_source
+ ) VALUES (
+ address,
+ decode(replace(duid, ':', ''), 'hex'),
+ valid_lifetime,
+ to_timestamp(expire),
+ subnet_id,
+ pref_lifetime,
+ lease_type,
+ iaid,
+ prefix_len,
+ fqdn_fwd::int::boolean,
+ fqdn_rev::int::boolean,
+ replace(hostname, '&#x2c', ','),
+ decode(replace(hwaddr, ':', ''), 'hex'),
+ state,
+ replace(user_context, '&#x2c', ','),
+ hwtype,
+ hwaddr_source
+ );
+END
+\$\$ LANGUAGE plpgsql;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '8', minor = '0';
+
+-- Commit the script transaction.
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_008_to_009.sh.in b/src/share/database/scripts/pgsql/upgrade_008_to_009.sh.in
new file mode 100644
index 0000000..b68ebf6
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_008_to_009.sh.in
@@ -0,0 +1,292 @@
+#!/bin/sh
+
+# Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "8.0" ]; then
+ printf 'This script upgrades 8.0 to 9.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+START TRANSACTION;
+
+-- This starts schema update to 9.0.
+
+-- Add missing cascade to constraint on dhcp4/6_subnet_server tables.
+ALTER TABLE dhcp4_subnet_server
+ DROP CONSTRAINT fk_dhcp4_subnet_server_server_id,
+ ADD CONSTRAINT fk_dhcp4_subnet_server_server_id
+ FOREIGN KEY (server_id) REFERENCES dhcp4_server (id) ON DELETE CASCADE ON UPDATE CASCADE,
+ DROP CONSTRAINT fk_dhcp4_subnet_server_subnet_id,
+ ADD CONSTRAINT fk_dhcp4_subnet_server_subnet_id
+ FOREIGN KEY (subnet_id) REFERENCES dhcp4_subnet (subnet_id) ON DELETE CASCADE ON UPDATE CASCADE;
+
+ALTER TABLE dhcp6_subnet_server
+ DROP CONSTRAINT fk_dhcp6_subnet_server_server_id,
+ ADD CONSTRAINT fk_dhcp6_subnet_server_server_id
+ FOREIGN KEY (server_id) REFERENCES dhcp6_server (id) ON DELETE CASCADE ON UPDATE CASCADE,
+ DROP CONSTRAINT fk_dhcp6_subnet_server_subnet_id,
+ ADD CONSTRAINT fk_dhcp6_subnet_server_subnet_id
+ FOREIGN KEY (subnet_id) REFERENCES dhcp6_subnet (subnet_id) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- Fix constraint typo on dhcp4_option_def_server
+ALTER TABLE dhcp4_option_def_server
+ DROP CONSTRAINT dhcp4_option_def_server_option_def_id_fkey,
+ ADD CONSTRAINT dhcp4_option_def_server_option_def_id_fkey
+ FOREIGN KEY (option_def_id) REFERENCES dhcp4_option_def(id) ON DELETE CASCADE;
+
+-- DROP shared-network ADEL triggers that should not exist.
+DROP TRIGGER IF EXISTS dhcp4_shared_network_ADEL on dhcp4_shared_network CASCADE;
+DROP TRIGGER IF EXISTS dhcp6_shared_network_ADEL on dhcp6_shared_network CASCADE;
+
+-- Replace createOptionAuditDHCP4() with a version that has local variable
+-- snid correctly declared as a BIGINT.
+--
+-- -----------------------------------------------------
+--
+-- Stored procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - p_subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - p_modification_ts: modification timestamp of the
+-- option.
+-- Some arguments are prefixed with "p_" to avoid ambiguity
+-- with column names in SQL statements. PostgreSQL does not
+-- allow table aliases to be used with column names in update
+-- set expressions.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP4(modification_type VARCHAR,
+ scope_id SMALLINT,
+ option_id INT,
+ p_subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR,
+ pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid BIGINT;
+ sid BIGINT;
+ cascade_transaction BOOLEAN;
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp4_options' is when a global option is added.
+ -- Global options do not have the owner.
+
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp4_options' table.
+ PERFORM createAuditEntryDHCP4('dhcp4_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, update
+ -- the modification timestamp of this subnet to allow the
+ -- servers to refresh the subnet information. This will
+ -- also result in creating an audit entry for this subnet.
+ UPDATE dhcp4_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = p_subnet_id;
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- update the modification timestamp of this shared network
+ -- to allow the servers to refresh the shared network
+ -- information. This will also result in creating an
+ -- audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp4_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp4_shared_network SET modification_ts = p_modification_ts
+ WHERE id = snid;
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, update
+ -- the modification timestamp of the owning subnet.
+ SELECT dhcp4_pool.subnet_id INTO sid FROM dhcp4_pool WHERE id = pool_id;
+ UPDATE dhcp4_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ END IF;
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- Replace createOptionAuditDHCP6() with a version that has local variable
+-- snid correctly declared as a BIGINT.
+--
+-- -----------------------------------------------------
+--
+-- Stored procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - p_subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- - p_modification_ts: modification timestamp of the
+-- option.
+-- Some arguments are prefixed with "p_" to avoid ambiguity
+-- with column names in SQL statements. PostgreSQL does not
+-- allow table aliases to be used with column names in update
+-- set expressions.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP6(modification_type VARCHAR,
+ scope_id SMALLINT,
+ option_id INT,
+ p_subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR,
+ pool_id BIGINT,
+ pd_pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid BIGINT;
+ sid BIGINT;
+ cascade_transaction BOOLEAN;
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp6_options' is when a global option is added.
+ -- Global options do not have the owner.
+
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp6_options' table.
+ PERFORM createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, update
+ -- the modification timestamp of this subnet to allow the
+ -- servers to refresh the subnet information. This will
+ -- also result in creating an audit entry for this subnet.
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = p_subnet_id;
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- update the modification timestamp of this shared network
+ -- to allow the servers to refresh the shared network
+ -- information. This will also result in creating an
+ -- audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp6_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp6_shared_network SET modification_ts = p_modification_ts
+ WHERE id = snid;
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, update
+ -- the modification timestamp of the owning subnet.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ ELSEIF scope_id = 6 THEN
+ -- If pd pool specific option is added or modified, create
+ -- audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ END IF;
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- Returns the epoch GMT time in second from a timestamp with time zone
+--
+-- param input_ts timestamp value to convert
+-- return a BIGINT containing the number of seconds since the epoch in GMT.
+CREATE OR REPLACE FUNCTION gmt_epoch(input_ts TIMESTAMP WITH TIME ZONE)
+RETURNS BIGINT
+AS \$\$
+DECLARE
+ gmt_epoch BIGINT;
+BEGIN
+ SELECT (extract(epoch from input_ts) + extract(timezone from input_ts))::BIGINT INTO gmt_epoch;
+ RETURN gmt_epoch;
+ EXCEPTION
+ WHEN OTHERS THEN
+ RAISE EXCEPTION 'gmt_epoch(%) : failed, sqlstate: %', input_ts, sqlstate;
+END;\$\$
+LANGUAGE plpgsql;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '9', minor = '0';
+
+-- Commit the script transaction.
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_009_to_010.sh.in b/src/share/database/scripts/pgsql/upgrade_009_to_010.sh.in
new file mode 100644
index 0000000..4c66c42
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_009_to_010.sh.in
@@ -0,0 +1,335 @@
+#!/bin/sh
+
+# Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "9.0" ]; then
+ printf 'This script upgrades 9.0 to 10.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+START TRANSACTION;
+
+-- This starts schema update to 10.0.
+-- It adds corrections for client classes for CB
+
+-- Replace setClientClass4Order():
+-- 1. l_depend_on_known_indirectly needs to be BOOL
+-- 2. follow_class_index needs to be BIGINT
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- new_follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - new_follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass4Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BOOL := false;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp4_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp4_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp4_client_class AS c
+ INNER JOIN dhcp4_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp4_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp4_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp4_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp4_client_class_dependency AS d
+ INNER JOIN dhcp4_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp4_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM - note that ON CONFLICT requires PostgreSQL 9.5 or later.
+ UPDATE dhcp4_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp4_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, false);
+ RETURN;
+END;\$\$;
+
+-- Replace setClientClass6Order():
+-- 1. l_depend_on_known_indirectly needs to be BOOL
+-- 2. follow_class_index needs to be BIGINT
+
+-- -----------------------------------------------------------------------
+-- Stored procedure positioning an inserted or updated client class
+-- within the class hierarchy, depending on the value of the
+-- new_follow_class_name parameter.
+--
+-- Parameters:
+-- - id id of the positioned class,
+-- - new_follow_class_name name of the class after which this class should be
+-- positioned within the class hierarchy.
+-- - old_follow_class_name previous name of the class after which this
+-- class was positioned within the class hierarchy.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION setClientClass6Order(id BIGINT,
+ new_follow_class_name VARCHAR(128),
+ old_follow_class_name VARCHAR(128))
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- Used to fetch class's current value for depend_on_known_indirectly
+ l_depend_on_known_indirectly BOOL := false;
+
+ -- Optionally set if the follow_class_name column value is specified.
+ follow_class_index BIGINT;
+BEGIN
+ -- Fetch the class's current value of depend_on_known_indirectly.
+ SELECT depend_on_known_indirectly INTO l_depend_on_known_indirectly
+ FROM dhcp6_client_class_order WHERE id = class_id;
+
+ -- Save it to the current session for use elsewhere during this transaction.
+ -- Note this does not work prior to Postgres 9.2 unless the variables are
+ -- defined in postgresql.conf. I think for now we put up with CB not supported
+ -- prior to 9.2 or we tell people how to edit the conf file.
+ PERFORM set_session_value('kea.depend_on_known_indirectly', l_depend_on_known_indirectly);
+
+ -- Bail if the class is updated without re-positioning.
+ IF(
+ l_depend_on_known_indirectly IS NOT NULL AND
+ ((new_follow_class_name IS NULL AND old_follow_class_name IS NULL) OR
+ (new_follow_class_name = old_follow_class_name))
+ ) THEN
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM should we update the session value also or is it moot?
+ UPDATE dhcp6_client_class_order SET depend_on_known_indirectly = false
+ WHERE class_id = id;
+ RETURN;
+ END IF;
+
+ IF new_follow_class_name IS NOT NULL THEN
+ -- Get the position of the class after which the new class should be added.
+ SELECT o.order_index INTO follow_class_index
+ FROM dhcp6_client_class AS c
+ INNER JOIN dhcp6_client_class_order AS o
+ ON c.id = o.class_id
+ WHERE c.name = new_follow_class_name;
+
+ IF follow_class_index IS NULL THEN
+ -- The class with a name specified with new_follow_class_name does
+ -- not exist.
+ RAISE EXCEPTION 'Class %s does not exist.', new_follow_class_name
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- We need to place the new class at the position of follow_class_index + 1.
+ -- There may be a class at this position already.
+ IF EXISTS(SELECT * FROM dhcp6_client_class_order WHERE order_index = follow_class_index + 1) THEN
+ -- There is a class at this position already. Let's move all classes
+ -- starting from this position by one to create a spot for the new
+ -- class.
+ UPDATE dhcp6_client_class_order
+ SET order_index = order_index + 1
+ WHERE order_index >= follow_class_index + 1;
+ -- TKM postgresql doesn't like order by here, does it matter?
+ -- ORDER BY order_index DESC;
+ END IF;
+
+ ELSE
+ -- A caller did not specify the new_follow_class_name value. Let's append the
+ -- new class at the end of the hierarchy.
+ SELECT MAX(order_index) INTO follow_class_index FROM dhcp6_client_class_order;
+ IF follow_class_index IS NULL THEN
+ -- Apparently, there are no classes. Let's start from 0.
+ follow_class_index = 0;
+ END IF;
+ END IF;
+
+ -- Check if moving the class doesn't break dependent classes.
+ IF EXISTS(
+ SELECT 1 FROM dhcp6_client_class_dependency AS d
+ INNER JOIN dhcp6_client_class_order AS o
+ ON d.class_id = o.class_id
+ WHERE d.dependency_id = id AND o.order_index < follow_class_index + 1
+ LIMIT 1
+ ) THEN
+ RAISE EXCEPTION 'Unable to move class with id %s because it would break its dependencies', id
+ USING ERRCODE = 'sql_routine_exception';
+ END IF;
+
+ -- The depend_on_known_indirectly is set to 0 because this procedure is invoked
+ -- whenever the dhcp6_client_class record is updated. Such update may include
+ -- test expression changes impacting the dependency on KNOWN/UNKNOWN classes.
+ -- This value will be later adjusted when dependencies are inserted.
+ -- TKM - note that ON CONFLICT requires PostgreSQL 9.5 or later.
+ UPDATE dhcp6_client_class_order
+ SET order_index = follow_class_index + 1,
+ depend_on_known_indirectly = l_depend_on_known_indirectly
+ WHERE class_id = id;
+ IF FOUND THEN
+ RETURN;
+ END IF;
+
+ INSERT INTO dhcp6_client_class_order(class_id, order_index, depend_on_known_indirectly)
+ VALUES (id, follow_class_index + 1, false);
+ RETURN;
+END;\$\$;
+
+-- Change primary key to composite, dependency table can have multiple rows
+-- per class id.
+ALTER TABLE dhcp4_client_class_dependency DROP CONSTRAINT dhcp4_client_class_dependency_pkey;
+ALTER TABLE dhcp4_client_class_dependency ADD PRIMARY KEY(class_id, dependency_id);
+
+ALTER TABLE dhcp6_client_class_dependency DROP CONSTRAINT dhcp6_client_class_dependency_pkey;
+ALTER TABLE dhcp6_client_class_dependency ADD PRIMARY KEY(class_id, dependency_id);
+
+-- Replace triggers that verify class dependency.
+-- Because they are BEFORE INSERT triggers they need to return NEW not NULL.
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp4_client_class_check_dependency_BINS()
+ RETURNS trigger AS \$dhcp4_client_class_check_dependency_BINS\$
+BEGIN
+ PERFORM checkDHCPv4ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NEW;
+END;
+\$dhcp4_client_class_check_dependency_BINS\$
+LANGUAGE plpgsql;
+
+-- -----------------------------------------------------------------------
+-- Trigger verifying if class dependency is met. It includes checking
+-- if referenced classes exist, are associated with the same server
+-- or all servers, and are defined before the class specified with
+-- class_id.
+-- -----------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION func_dhcp6_client_class_check_dependency_BINS()
+ RETURNS trigger AS \$dhcp6_client_class_check_dependency_BINS\$
+BEGIN
+ PERFORM checkDHCPv6ClientClassDependency(NEW.class_id, NEW.dependency_id);
+ RETURN NEW;
+END;
+\$dhcp6_client_class_check_dependency_BINS\$
+LANGUAGE plpgsql;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '10', minor = '0';
+
+-- Commit the script transaction.
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_010_to_011.sh.in b/src/share/database/scripts/pgsql/upgrade_010_to_011.sh.in
new file mode 100644
index 0000000..6252657
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_010_to_011.sh.in
@@ -0,0 +1,152 @@
+#!/bin/sh
+
+# Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "10.0" ]; then
+ printf 'This script upgrades 10.0 to 11.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+START TRANSACTION;
+
+-- This starts schema update to 11.0.
+
+-- Replace createOptionAuditDHCP6() with a version corrected
+-- where clause when scope is 6 (i.e. PD pool)
+--
+-- -----------------------------------------------------
+--
+-- Stored procedure which updates modification timestamp of
+-- a parent object when an option is modified.
+--
+-- The following parameters are passed to the procedure:
+-- - modification_type: "create", "update" or "delete"
+-- - scope_id: identifier of the option scope, e.g.
+-- global, subnet specific etc. See dhcp_option_scope
+-- for specific values.
+-- - option_id: identifier of the option.
+-- - p_subnet_id: identifier of the subnet if the option
+-- belongs to the subnet.
+-- - host_id: identifier of the host if the option
+-- - belongs to the host.
+-- - network_name: shared network name if the option
+-- belongs to the shared network.
+-- - pool_id: identifier of the pool if the option
+-- belongs to the pool.
+-- - pd_pool_id: identifier of the pool if the option
+-- belongs to the pd pool.
+-- - p_modification_ts: modification timestamp of the
+-- option.
+-- Some arguments are prefixed with "p_" to avoid ambiguity
+-- with column names in SQL statements. PostgreSQL does not
+-- allow table aliases to be used with column names in update
+-- set expressions.
+-- -----------------------------------------------------
+CREATE OR REPLACE FUNCTION createOptionAuditDHCP6(modification_type VARCHAR,
+ scope_id SMALLINT,
+ option_id INT,
+ p_subnet_id BIGINT,
+ host_id INT,
+ network_name VARCHAR,
+ pool_id BIGINT,
+ pd_pool_id BIGINT,
+ p_modification_ts TIMESTAMP WITH TIME ZONE)
+RETURNS VOID
+LANGUAGE plpgsql
+AS \$\$
+DECLARE
+ -- These variables will hold shared network id and subnet id that
+ -- we will select.
+ snid BIGINT;
+ sid BIGINT;
+ cascade_transaction BOOLEAN;
+BEGIN
+ -- Cascade transaction flag is set to true to prevent creation of
+ -- the audit entries for the options when the options are
+ -- created as part of the parent object creation or update.
+ -- For example: when the option is added as part of the subnet
+ -- addition, the cascade transaction flag is equal to true. If
+ -- the option is added into the existing subnet the cascade
+ -- transaction is equal to false. Note that depending on the option
+ -- scope the audit entry will contain the object_type value
+ -- of the parent object to cause the server to replace the
+ -- entire subnet. The only case when the object_type will be
+ -- set to 'dhcp6_options' is when a global option is added.
+ -- Global options do not have the owner.
+
+ cascade_transaction := get_session_boolean('kea.cascade_transaction');
+ IF cascade_transaction = false THEN
+ -- todo: host manager hasn't been updated to use audit
+ -- mechanisms so ignore host specific options for now.
+ IF scope_id = 0 THEN
+ -- If a global option is added or modified, create audit
+ -- entry for the 'dhcp6_options' table.
+ PERFORM createAuditEntryDHCP6('dhcp6_options', option_id, modification_type);
+ ELSEIF scope_id = 1 THEN
+ -- If subnet specific option is added or modified, update
+ -- the modification timestamp of this subnet to allow the
+ -- servers to refresh the subnet information. This will
+ -- also result in creating an audit entry for this subnet.
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = p_subnet_id;
+ ELSEIF scope_id = 4 THEN
+ -- If shared network specific option is added or modified,
+ -- update the modification timestamp of this shared network
+ -- to allow the servers to refresh the shared network
+ -- information. This will also result in creating an
+ -- audit entry for this shared network.
+ SELECT id INTO snid FROM dhcp6_shared_network WHERE name = network_name LIMIT 1;
+ UPDATE dhcp6_shared_network SET modification_ts = p_modification_ts
+ WHERE id = snid;
+ ELSEIF scope_id = 5 THEN
+ -- If pool specific option is added or modified, update
+ -- the modification timestamp of the owning subnet.
+ SELECT dhcp6_pool.subnet_id INTO sid FROM dhcp6_pool WHERE id = pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ ELSEIF scope_id = 6 THEN
+ -- If pd pool specific option is added or modified, create
+ -- audit entry for the subnet which this pool belongs to.
+ SELECT dhcp6_pd_pool.subnet_id INTO sid FROM dhcp6_pd_pool WHERE id = pd_pool_id;
+ UPDATE dhcp6_subnet SET modification_ts = p_modification_ts
+ WHERE subnet_id = sid;
+ END IF;
+ END IF;
+ RETURN;
+END;\$\$;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '11', minor = '0';
+
+-- Commit the script transaction.
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_011_to_012.sh.in b/src/share/database/scripts/pgsql/upgrade_011_to_012.sh.in
new file mode 100644
index 0000000..00a02dc
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_011_to_012.sh.in
@@ -0,0 +1,106 @@
+#!/bin/sh
+
+# Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "11.0" ]; then
+ printf 'This script upgrades 11.0 to 12.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+START TRANSACTION;
+
+-- This line starts the schema upgrade to version 12.
+
+-- Modify shared-network-name foreign key constraint on dhcp4_subnet to not perform
+-- the update when the network is deleted the cascaded update will not execute
+-- dhcp4_subnet update trigger leaving the updated subnets without audit_entries.
+ALTER TABLE dhcp4_subnet
+ DROP CONSTRAINT fk_dhcp4_subnet_shared_network,
+ ADD CONSTRAINT fk_dhcp4_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp4_shared_network (name)
+ ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+-- Modify BEFORE delete trigger function on dhcp4_shared_network to explicitly
+-- update dhcp4_subnets. This ensures there are audit entries for updated
+-- subnets.
+-- Trigger function for dhcp4_shared_network_BDEL called BEFORE DELETE on dhcp4_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp4_shared_network_BDEL() RETURNS TRIGGER AS \$dhcp4_shared_network_BDEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP4('dhcp4_shared_network', OLD.id, 'delete');
+ -- Explicitly update subnets now rather than via foreign key constraint, this ensures the
+ -- audit entries for subnets will preceded that of the shared-network, keeping the order
+ -- of the entries the same as they are for MySQL.
+ UPDATE dhcp4_subnet SET shared_network_name = NULL WHERE shared_network_name = OLD.name;
+ DELETE FROM dhcp4_options WHERE shared_network_name = OLD.name;
+ RETURN OLD;
+END;
+\$dhcp4_shared_network_BDEL\$
+LANGUAGE plpgsql;
+
+-- Modify shared-network-name foreign key constraint on dhcp6_subnet to not perform
+-- the update when the network is deleted the cascaded update will not execute
+-- dhcp6_subnet update trigger leaving the updated subnets without audit_entries.
+ALTER TABLE dhcp6_subnet
+ DROP CONSTRAINT fk_dhcp6_subnet_shared_network,
+ ADD CONSTRAINT fk_dhcp6_subnet_shared_network FOREIGN KEY (shared_network_name)
+ REFERENCES dhcp6_shared_network (name)
+ ON DELETE NO ACTION ON UPDATE NO ACTION;
+
+-- Modify BEFORE delete trigger function on dhcp6_shared_network to explicitly
+-- update dhcp6_subnets. This ensures there are audit entries for updated
+-- subnets.
+-- Trigger function for dhcp6_shared_network_BDEL called BEFORE DELETE on dhcp6_shared_network
+CREATE OR REPLACE FUNCTION func_dhcp6_shared_network_BDEL() RETURNS TRIGGER AS \$dhcp6_shared_network_BDEL\$
+BEGIN
+ PERFORM createAuditEntryDHCP6('dhcp6_shared_network', OLD.id, 'delete');
+ -- Explicitly update subnets now rather than via foreign key constraint, this ensures the
+ -- audit entries for subnets will preceded that of the shared-network, keeping the order
+ -- of the entries the same as they are for MySQL.
+ UPDATE dhcp6_subnet SET shared_network_name = NULL WHERE shared_network_name = OLD.name;
+ DELETE FROM dhcp6_options WHERE shared_network_name = OLD.name;
+ RETURN OLD;
+END;
+\$dhcp6_shared_network_BDEL\$
+LANGUAGE plpgsql;
+
+-- Add user_context column to client class tables.
+ALTER TABLE dhcp4_client_class ADD COLUMN user_context JSON DEFAULT NULL;
+ALTER TABLE dhcp6_client_class ADD COLUMN user_context JSON DEFAULT NULL;
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '12', minor = '0';
+
+-- This line concludes the schema upgrade to version 12.
+
+-- Commit the script transaction.
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/upgrade_012_to_013.sh.in b/src/share/database/scripts/pgsql/upgrade_012_to_013.sh.in
new file mode 100644
index 0000000..bb45b9e
--- /dev/null
+++ b/src/share/database/scripts/pgsql/upgrade_012_to_013.sh.in
@@ -0,0 +1,681 @@
+#!/bin/sh
+
+# Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+VERSION=$(pgsql_version "$@")
+
+if [ "$VERSION" != "12.0" ]; then
+ printf 'This script upgrades 12.0 to 12.0. '
+ printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
+ exit 0
+fi
+
+psql "$@" >/dev/null <<EOF
+START TRANSACTION;
+
+-- This line starts the schema upgrade to version 13.
+
+-- JSON functions --
+
+-- Helper function that avoids a casting error when the string
+-- presumed to be in JSON format, is empty.
+CREATE OR REPLACE FUNCTION json_cast(IN json_candidate TEXT)
+RETURNS JSON
+AS \$\$
+BEGIN
+ IF LENGTH(json_candidate) = 0 THEN
+ RETURN '{}'::json;
+ END IF;
+ RETURN json_candidate::json;
+END;
+\$\$ LANGUAGE plpgsql;
+
+-- Function that establishes whether JSON functions are supported.
+-- They should be provided with PostgreSQL >= 9.4.
+CREATE OR REPLACE FUNCTION isJsonSupported()
+RETURNS BOOLEAN
+AS \$\$
+BEGIN
+ IF get_session_value('json_supported') IS NULL THEN
+ IF (SELECT proname FROM pg_proc WHERE proname = 'json_extract_path') = 'json_extract_path' THEN
+ PERFORM set_session_value('kea.json_supported', true);
+ ELSE
+ PERFORM set_session_value('kea.json_supported', false);
+ END IF;
+ END IF;
+ RETURN get_session_value('kea.json_supported');
+END
+\$\$ LANGUAGE plpgsql;
+
+-- Schema changes related to lease limiting start here. --
+
+-- Recreate the triggers that update the leaseX_stat tables as stored procedures. --
+
+CREATE OR REPLACE FUNCTION lease4_AINS_lease4_stat(IN new_state BIGINT,
+ IN new_subnet_id BIGINT)
+RETURNS VOID
+AS \$\$
+BEGIN
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Update the state count if it exists.
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND state = new_state;
+
+ -- Insert the state count record if it does not exist.
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat VALUES (new_subnet_id, new_state, 1);
+ END IF;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease4_AUPD_lease4_stat(IN old_state BIGINT,
+ IN old_subnet_id BIGINT,
+ IN new_state BIGINT,
+ IN new_subnet_id BIGINT)
+RETURNS VOID
+AS \$\$
+BEGIN
+ IF old_subnet_id != new_subnet_id OR old_state != new_state THEN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the old state count if record exists.
+ UPDATE lease4_stat
+ SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND state = old_state;
+ END IF;
+
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Increment the new state count if record exists.
+ UPDATE lease4_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND state = new_state;
+
+ -- Insert new state record if it does not exist.
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat VALUES (new_subnet_id, new_state, 1);
+ END IF;
+ END IF;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease4_ADEL_lease4_stat(IN old_state BIGINT,
+ IN old_subnet_id BIGINT)
+RETURNS VOID
+AS \$\$
+BEGIN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the state count if record exists.
+ UPDATE lease4_stat
+ SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND old_state = state;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_AINS_lease6_stat(IN new_state BIGINT,
+ IN new_subnet_id BIGINT,
+ IN new_lease_type SMALLINT)
+RETURNS VOID
+AS \$\$
+BEGIN
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Update the state count if it exists.
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND lease_type = new_lease_type
+ AND state = new_state;
+
+ -- Insert the state count record if it does not exist.
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat VALUES (new_subnet_id, new_lease_type, new_state, 1);
+ END IF;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_AUPD_lease6_stat(IN old_state BIGINT,
+ IN old_subnet_id BIGINT,
+ IN old_lease_type SMALLINT,
+ IN new_state BIGINT,
+ IN new_subnet_id BIGINT,
+ IN new_lease_type SMALLINT)
+RETURNS VOID
+AS \$\$
+BEGIN
+ IF old_subnet_id != new_subnet_id OR
+ old_lease_type != new_lease_type OR
+ old_state != new_state THEN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the old state count if record exists.
+ UPDATE lease6_stat
+ SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND lease_type = old_lease_type
+ AND state = old_state;
+ END IF;
+
+ IF new_state = 0 OR new_state = 1 THEN
+ -- Increment the new state count if record exists
+ UPDATE lease6_stat SET leases = leases + 1
+ WHERE subnet_id = new_subnet_id AND lease_type = new_lease_type
+ AND state = new_state;
+
+ -- Insert new state record if it does not exist
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat
+ VALUES (new_subnet_id, new_lease_type, new_state, 1);
+ END IF;
+ END IF;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_ADEL_lease6_stat(IN old_state BIGINT,
+ IN old_subnet_id BIGINT,
+ IN old_lease_type SMALLINT)
+RETURNS VOID
+AS \$\$
+BEGIN
+ IF old_state = 0 OR old_state = 1 THEN
+ -- Decrement the state count if record exists
+ UPDATE lease6_stat
+ SET leases = GREATEST(leases - 1, 0)
+ WHERE subnet_id = old_subnet_id AND lease_type = old_lease_type
+ AND state = old_state;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+-- Create tables that contain the number of active leases. --
+
+CREATE TABLE lease4_stat_by_client_class (
+ client_class VARCHAR(128) NOT NULL PRIMARY KEY,
+ leases BIGINT NOT NULL
+);
+
+CREATE TABLE lease6_stat_by_client_class (
+ client_class VARCHAR(128) NOT NULL,
+ lease_type SMALLINT NOT NULL,
+ leases BIGINT NOT NULL,
+ PRIMARY KEY (client_class, lease_type),
+ CONSTRAINT fk_lease6_stat_by_client_class_lease_type FOREIGN KEY (lease_type)
+ REFERENCES lease6_types (lease_type)
+);
+
+-- Create procedures to be called for each row in after-event triggers for
+-- INSERT, UPDATE and DELETE on lease tables.
+
+CREATE OR REPLACE FUNCTION lease4_AINS_lease4_stat_by_client_class(IN new_state BIGINT,
+ IN new_user_context TEXT)
+RETURNS VOID
+AS \$\$
+DECLARE
+ class VARCHAR(128);
+BEGIN
+ -- Only state 0 is needed for lease limiting.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(new_user_context)->'ISC'->'client-classes') LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Upsert to increment the lease count.
+ UPDATE lease4_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class;
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat_by_client_class VALUES (class, 1);
+ END IF;
+ END LOOP;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease4_AUPD_lease4_stat_by_client_class(IN old_state BIGINT,
+ IN old_user_context TEXT,
+ IN new_state BIGINT,
+ IN new_user_context TEXT)
+RETURNS VOID
+AS \$\$
+DECLARE
+ old_client_classes TEXT;
+ new_client_classes TEXT;
+ class VARCHAR(128);
+ length INT;
+ i INT;
+BEGIN
+ SELECT json_cast(old_user_context)->'ISC'->'client-classes' INTO old_client_classes;
+ SELECT json_cast(new_user_context)->'ISC'->'client-classes' INTO new_client_classes;
+
+ IF old_state != new_state OR old_client_classes != new_client_classes THEN
+ -- Check if it's moving away from a counted state.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(old_client_classes)) LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease4_stat_by_client_class SET leases = GREATEST(leases - 1, 0)
+ WHERE client_class = class;
+ END LOOP;
+ END IF;
+
+ -- Check if it's moving into a counted state.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(new_client_classes)) LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Upsert to increment the lease count.
+ UPDATE lease4_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class;
+ IF NOT FOUND THEN
+ INSERT INTO lease4_stat_by_client_class VALUES (class, 1);
+ END IF;
+ END LOOP;
+ END IF;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease4_ADEL_lease4_stat_by_client_class(IN old_state BIGINT,
+ IN old_user_context TEXT)
+RETURNS VOID
+AS \$\$
+DECLARE
+ class VARCHAR(128);
+BEGIN
+ -- Only state 0 is accounted for in lease limiting.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(old_user_context)->'ISC'->'client-classes') LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease4_stat_by_client_class SET leases = GREATEST(leases - 1, 0)
+ WHERE client_class = class;
+ END LOOP;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_AINS_lease6_stat_by_client_class(IN new_state BIGINT,
+ IN new_user_context TEXT,
+ IN new_lease_type SMALLINT)
+RETURNS VOID
+AS \$\$
+DECLARE
+ client_classes TEXT;
+ class VARCHAR(128);
+ length INT;
+ i INT;
+BEGIN
+ -- Only state 0 is needed for lease limiting.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(new_user_context)->'ISC'->'client-classes') LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Upsert to increment the lease count.
+ UPDATE lease6_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class AND lease_type = new_lease_type;
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat_by_client_class VALUES (class, new_lease_type, 1);
+ END IF;
+ END LOOP;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_AUPD_lease6_stat_by_client_class(IN old_state BIGINT,
+ IN old_user_context TEXT,
+ IN old_lease_type SMALLINT,
+ IN new_state BIGINT,
+ IN new_user_context TEXT,
+ IN new_lease_type SMALLINT)
+RETURNS VOID
+AS \$\$
+DECLARE
+ old_client_classes TEXT;
+ new_client_classes TEXT;
+ class VARCHAR(128);
+ length INT;
+ i INT;
+BEGIN
+ SELECT json_cast(old_user_context)->'ISC'->'client-classes' INTO old_client_classes;
+ SELECT json_cast(new_user_context)->'ISC'->'client-classes' INTO new_client_classes;
+
+ IF old_state != new_state OR old_client_classes != new_client_classes OR old_lease_type != new_lease_type THEN
+ -- Check if it's moving away from a counted state.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(old_client_classes)) LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease6_stat_by_client_class SET leases = GREATEST(leases - 1, 0)
+ WHERE client_class = class AND lease_type = old_lease_type;
+ END LOOP;
+ END IF;
+
+ -- Check if it's moving into a counted state.
+ IF new_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(new_client_classes)) LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Upsert to increment the lease count.
+ UPDATE lease6_stat_by_client_class SET leases = leases + 1
+ WHERE client_class = class AND lease_type = new_lease_type;
+ IF NOT FOUND THEN
+ INSERT INTO lease6_stat_by_client_class VALUES (class, new_lease_type, 1);
+ END IF;
+ END LOOP;
+ END IF;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION lease6_ADEL_lease6_stat_by_client_class(IN old_state BIGINT,
+ IN old_user_context TEXT,
+ IN old_lease_type SMALLINT)
+RETURNS VOID
+AS \$\$
+DECLARE
+ client_classes VARCHAR(1024);
+ class VARCHAR(128);
+ length INT;
+ i INT;
+BEGIN
+ -- Only state 0 is accounted for in lease limiting. But check both states to be consistent with lease6_stat.
+ IF old_state = 0 THEN
+ -- Dive into client classes.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(old_user_context)->'ISC'->'client-classes') LOOP
+ SELECT TRIM('"' FROM class) INTO class;
+
+ -- Decrement the lease count if the record exists.
+ UPDATE lease6_stat_by_client_class SET leases = GREATEST(leases - 1, 0)
+ WHERE client_class = class AND lease_type = old_lease_type;
+ END LOOP;
+ END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+
+-- Recreate the after-event triggers for INSERT, UPDATE and DELETE on lease tables to call the --
+-- stored procedures above in pairs of two: for client classes and for subnets. --
+
+DROP TRIGGER IF EXISTS stat_lease4_insert ON lease4;
+
+CREATE OR REPLACE FUNCTION func_lease4_AINS()
+RETURNS trigger AS \$lease4_AINS\$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease4_AINS_lease4_stat_by_client_class(NEW.state, NEW.user_context);
+ END IF;
+ PERFORM lease4_AINS_lease4_stat(NEW.state, NEW.subnet_id);
+ RETURN NULL;
+END;
+\$lease4_AINS\$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease4_AINS AFTER INSERT ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE func_lease4_AINS();
+
+DROP TRIGGER IF EXISTS stat_lease4_update ON lease4;
+
+CREATE OR REPLACE FUNCTION func_lease4_AUPD()
+RETURNS trigger AS \$lease4_AUPD\$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease4_AUPD_lease4_stat_by_client_class(OLD.state, OLD.user_context, NEW.state, NEW.user_context);
+ END IF;
+ PERFORM lease4_AUPD_lease4_stat(OLD.state, OLD.subnet_id, NEW.state, NEW.subnet_id);
+ RETURN NULL;
+END;
+\$lease4_AUPD\$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease4_AUPD AFTER UPDATE ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE func_lease4_AUPD();
+
+DROP TRIGGER IF EXISTS stat_lease4_delete ON lease4;
+
+CREATE OR REPLACE FUNCTION func_lease4_ADEL()
+RETURNS trigger AS \$lease4_ADEL\$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease4_ADEL_lease4_stat_by_client_class(OLD.state, OLD.user_context);
+ END IF;
+ PERFORM lease4_ADEL_lease4_stat(OLD.state, OLD.subnet_id);
+ RETURN NULL;
+END;
+\$lease4_ADEL\$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease4_ADEL AFTER DELETE ON lease4
+ FOR EACH ROW EXECUTE PROCEDURE func_lease4_ADEL();
+
+DROP TRIGGER IF EXISTS stat_lease6_insert ON lease6;
+
+CREATE OR REPLACE FUNCTION func_lease6_AINS()
+RETURNS trigger AS \$lease6_AINS\$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease6_AINS_lease6_stat_by_client_class(NEW.state, NEW.user_context, NEW.lease_type);
+ END IF;
+ PERFORM lease6_AINS_lease6_stat(NEW.state, NEW.subnet_id, NEW.lease_type);
+ RETURN NULL;
+END;
+\$lease6_AINS\$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease6_AINS AFTER INSERT ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE func_lease6_AINS();
+
+DROP TRIGGER IF EXISTS stat_lease6_update ON lease6;
+
+CREATE OR REPLACE FUNCTION func_lease6_AUPD()
+RETURNS trigger AS \$lease6_AUPD\$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease6_AUPD_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type, NEW.state, NEW.user_context, NEW.lease_type);
+ END IF;
+ PERFORM lease6_AUPD_lease6_stat(OLD.state, OLD.subnet_id, OLD.lease_type, NEW.state, NEW.subnet_id, NEW.lease_type);
+ RETURN NULL;
+END;
+\$lease6_AUPD\$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease6_AUPD AFTER UPDATE ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE func_lease6_AUPD();
+
+DROP TRIGGER IF EXISTS stat_lease6_delete ON lease6;
+
+CREATE OR REPLACE FUNCTION func_lease6_ADEL()
+RETURNS trigger AS \$lease6_ADEL\$
+BEGIN
+ IF isJsonSupported() = true THEN
+ PERFORM lease6_ADEL_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type);
+ END IF;
+ PERFORM lease6_ADEL_lease6_stat(OLD.state, OLD.subnet_id, OLD.lease_type);
+ RETURN NULL;
+END;
+\$lease6_ADEL\$ LANGUAGE plpgsql;
+
+CREATE TRIGGER lease6_ADEL AFTER DELETE ON lease6
+ FOR EACH ROW EXECUTE PROCEDURE func_lease6_ADEL();
+
+-- Create functions that return an empty TEXT if all limits allow for more leases, or otherwise a
+-- TEXT in one of the following JSON formats detailing the limit that was reached:
+-- { "limit-type": "client-class", "name": foo, "lease-type": "address", "limit": 2, "count": 2 }
+-- { "limit-type": "subnet", "id": 1, "lease-type": "IA_PD", "limit": 2, "count": 2 }
+-- The following format for user_context is assumed:
+-- { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+-- "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+
+CREATE OR REPLACE FUNCTION checkLease4Limits(user_context TEXT)
+RETURNS TEXT
+AS \$\$
+DECLARE
+ class TEXT;
+ name VARCHAR(255);
+ sid INT;
+ lease_limit INT;
+ lease_count INT;
+BEGIN
+ -- Dive into client class limits.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(user_context)->'ISC'->'limits'->'client-classes') LOOP
+ SELECT TRIM('"' FROM (json_cast(class)->'name')::text) INTO name;
+ SELECT json_cast(class)->'address-limit' INTO lease_limit;
+
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SELECT leases FROM lease4_stat_by_client_class INTO lease_count WHERE client_class = name;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+ END LOOP;
+
+ -- Dive into subnet limits.
+ SELECT json_cast(user_context)->'ISC'->'limits'->'subnet'->'id' INTO sid;
+ SELECT json_cast(user_context)->'ISC'->'limits'->'subnet'->'address-limit' INTO lease_limit;
+
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SELECT leases FROM lease4_stat WHERE subnet_id = sid AND state = 0 INTO lease_count;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for subnet ID ', sid, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ RETURN '';
+END;
+\$\$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION checkLease6Limits(user_context TEXT)
+RETURNS TEXT
+AS \$\$
+DECLARE
+ class TEXT;
+ name VARCHAR(255);
+ sid INT;
+ lease_limit INT;
+ lease_count INT;
+BEGIN
+ -- Dive into client class limits.
+ FOR class IN SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(user_context)->'ISC'->'limits'->'client-classes') LOOP
+ SELECT TRIM('"' FROM (json_cast(class)->'name')::text) INTO name;
+ SELECT json_cast(class)->'address-limit' INTO lease_limit;
+
+ IF lease_limit IS NOT NULL THEN
+ -- Get the address count for this client class.
+ SELECT leases FROM lease6_stat_by_client_class WHERE client_class = name AND lease_type = 0 INTO lease_count;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ SELECT json_cast(class)->'prefix-limit' INTO lease_limit;
+ IF lease_limit IS NOT NULL THEN
+ -- Get the prefix count for this client class.
+ SELECT leases FROM lease6_stat_by_client_class WHERE client_class = name AND lease_type = 2 INTO lease_count;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('prefix limit ', lease_limit, ' for client class "', name, '", current lease count ', lease_count);
+ END IF;
+ END IF;
+ END LOOP;
+
+ -- Dive into subnet limits.
+ SELECT json_cast(user_context)->'ISC'->'limits'->'subnet'->'id' INTO sid;
+ SELECT json_cast(user_context)->'ISC'->'limits'->'subnet'->'address-limit' INTO lease_limit;
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this subnet.
+ SELECT leases FROM lease6_stat WHERE subnet_id = sid AND lease_type = 0 AND state = 0 INTO lease_count;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('address limit ', lease_limit, ' for subnet ID ', sid, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+ SELECT json_cast(user_context)->'ISC'->'limits'->'subnet'->'prefix-limit' INTO lease_limit;
+ IF lease_limit IS NOT NULL THEN
+ -- Get the lease count for this client class.
+ SELECT leases FROM lease6_stat WHERE subnet_id = sid AND lease_type = 2 AND state = 0 INTO lease_count;
+ IF lease_count IS NULL THEN
+ lease_count := 0;
+ END IF;
+
+ -- Compare. Return immediately if the limit is surpassed.
+ IF lease_limit <= lease_count THEN
+ RETURN CONCAT('prefix limit ', lease_limit, ' for subnet ID ', sid, ', current lease count ', lease_count);
+ END IF;
+ END IF;
+
+ RETURN '';
+END;
+\$\$ LANGUAGE plpgsql;
+
+-- Improve hosts indexes for better performance of global reservations
+-- Create new index that uses only dhcp_identifier.
+CREATE INDEX key_dhcp_identifier on hosts (dhcp_identifier, dhcp_identifier_type);
+
+-- Modify existing indexes to include subnet_id values of 0, so index is also used
+-- for global reservations.
+DROP INDEX IF EXISTS key_dhcp4_identifier_subnet_id;
+CREATE UNIQUE INDEX key_dhcp4_identifier_subnet_id ON hosts
+ (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp4_subnet_id ASC)
+ WHERE (dhcp4_subnet_id IS NOT NULL);
+
+DROP INDEX IF EXISTS key_dhcp6_identifier_subnet_id;
+CREATE UNIQUE INDEX key_dhcp6_identifier_subnet_id ON hosts
+ (dhcp_identifier ASC, dhcp_identifier_type ASC, dhcp6_subnet_id ASC)
+ WHERE (dhcp6_subnet_id IS NOT NULL);
+
+-- Update the schema version number.
+UPDATE schema_version
+ SET version = '13', minor = '0';
+
+-- This line concludes the schema upgrade to version 13.
+
+-- Commit the script transaction.
+COMMIT;
+
+EOF
diff --git a/src/share/database/scripts/pgsql/wipe_data.sh.in b/src/share/database/scripts/pgsql/wipe_data.sh.in
new file mode 100644
index 0000000..088b802
--- /dev/null
+++ b/src/share/database/scripts/pgsql/wipe_data.sh.in
@@ -0,0 +1,117 @@
+#!/bin/sh
+
+# Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# This script is primarily used for MySQL unit tests, which need to
+# ensure an empty, but schema correct database for each test. It
+# deletes ALL transient data from an existing Kea MySQL schema,
+# including leases, reservations, etc... Use at your own peril.
+# Reference tables will be left in-tact.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+prefix="@prefix@"
+
+# Include utilities. Use installed version if available and
+# use build version if it isn't.
+if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then
+ . "@datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh"
+else
+ . "@abs_top_builddir@/src/bin/admin/admin-utils.sh"
+fi
+
+# First argument must be the expected schema version <major>.<minor>
+# Check if it's passed at all.
+if [ "$#" -lt "1" ]; then
+ printf "Required at least one parameter: schema version number, e.g. 7.0\n"
+ exit 1
+fi
+exp_version="$1"
+shift
+
+# Remaining arguments are used as pgsql command line arguments
+
+# If the existing schema doesn't match, the fail
+VERSION=$(pgsql_version "$@")
+if [ "$VERSION" = "" ]; then
+ printf "Cannot wipe data, schema version could not be detected.\n"
+ exit 1
+fi
+
+if [ "$VERSION" != "$exp_version" ]; then
+ printf 'Cannot wipe data, wrong schema version. '
+ printf 'Expected version %s, found %s.\n' "${exp_version}" "${VERSION}"
+ exit 1
+fi
+
+# Delete transient data from tables. We're using delete instead
+# of truncate because it is much faster since our unit tests
+# create very little data.
+# Note we disable revision auditing to avoid issues with delete
+# triggers.
+psql "$@" >/dev/null <<EOF
+SELECT set_config('kea.disable_audit', 'true', false);
+START TRANSACTION;
+DELETE FROM hosts CASCADE;
+DELETE FROM dhcp4_options;
+DELETE FROM ipv6_reservations;
+DELETE FROM dhcp6_options;
+DELETE FROM lease4;
+DELETE FROM lease4_stat;
+DELETE FROM lease6;
+DELETE FROM lease6_stat;
+DELETE FROM logs;
+DELETE FROM lease4_stat_by_client_class;
+DELETE FROM lease6_stat_by_client_class;
+
+-- Config Backend tables
+DELETE FROM dhcp4_audit;
+DELETE FROM dhcp4_audit_revision;
+DELETE FROM dhcp4_global_parameter;
+DELETE FROM dhcp4_global_parameter_server;
+DELETE FROM dhcp4_option_def;
+DELETE FROM dhcp4_option_def_server;
+DELETE FROM dhcp4_options;
+DELETE FROM dhcp4_options_server;
+DELETE FROM dhcp4_pool;
+DELETE FROM dhcp4_shared_network;
+DELETE FROM dhcp4_subnet;
+DELETE FROM dhcp4_subnet_server;
+DELETE FROM dhcp4_shared_network_server;
+DELETE FROM dhcp4_client_class_order;
+DELETE FROM dhcp4_client_class_dependency;
+DELETE FROM dhcp4_client_class_server;
+DELETE FROM dhcp4_client_class;
+DELETE FROM dhcp4_server WHERE tag != 'all'; -- preserve the special tag
+
+DELETE FROM dhcp6_audit;
+DELETE FROM dhcp6_audit_revision;
+DELETE FROM dhcp6_global_parameter;
+DELETE FROM dhcp6_global_parameter_server;
+DELETE FROM dhcp6_option_def;
+DELETE FROM dhcp6_option_def_server;
+DELETE FROM dhcp6_options;
+DELETE FROM dhcp6_options_server;
+DELETE FROM dhcp6_pool;
+DELETE FROM dhcp6_pd_pool;
+DELETE FROM dhcp6_shared_network;
+DELETE FROM dhcp6_subnet;
+DELETE FROM dhcp6_client_class_order;
+DELETE FROM dhcp6_client_class_dependency;
+DELETE FROM dhcp6_client_class_server;
+DELETE FROM dhcp6_client_class;
+DELETE FROM dhcp6_server WHERE tag != 'all'; -- preserve the special tag
+
+COMMIT;
+EOF
diff --git a/src/share/yang/Makefile.am b/src/share/yang/Makefile.am
new file mode 100644
index 0000000..c8c0085
--- /dev/null
+++ b/src/share/yang/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = modules
diff --git a/src/share/yang/Makefile.in b/src/share/yang/Makefile.in
new file mode 100644
index 0000000..3bd8697
--- /dev/null
+++ b/src/share/yang/Makefile.in
@@ -0,0 +1,704 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/share/yang
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = modules
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/share/yang/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/share/yang/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/share/yang/modules/Makefile.am b/src/share/yang/modules/Makefile.am
new file mode 100644
index 0000000..9a2f88d
--- /dev/null
+++ b/src/share/yang/modules/Makefile.am
@@ -0,0 +1,26 @@
+SUBDIRS = utils
+
+yangmodulesdir = ${datarootdir}/${PACKAGE_NAME}/yang/modules
+
+yangmodules_list =
+yangmodules_list += ietf-dhcpv6-client@2018-09-04.yang
+yangmodules_list += ietf-interfaces@2018-02-20.yang
+yangmodules_list += ietf-dhcpv6-options@2018-09-04.yang
+yangmodules_list += ietf-dhcpv6-relay@2018-09-04.yang
+yangmodules_list += ietf-dhcpv6-server@2018-09-04.yang
+yangmodules_list += ietf-dhcpv6-types@2018-09-04.yang
+yangmodules_list += ietf-inet-types@2013-07-15.yang
+yangmodules_list += ietf-yang-types@2013-07-15.yang
+yangmodules_list += kea-ctrl-agent@2019-08-12.yang
+yangmodules_list += kea-dhcp-ddns@2022-07-27.yang
+yangmodules_list += kea-dhcp-types@2022-07-27.yang
+yangmodules_list += kea-dhcp4-server@2022-07-27.yang
+yangmodules_list += kea-dhcp6-server@2022-07-27.yang
+yangmodules_list += kea-types@2019-08-12.yang
+yangmodules_list += keatest-module@2018-11-20.yang
+
+EXTRA_DIST = hashes ${yangmodules_list}
+
+if HAVE_SYSREPO
+yangmodules_DATA = ${yangmodules_list}
+endif
diff --git a/src/share/yang/modules/Makefile.in b/src/share/yang/modules/Makefile.in
new file mode 100644
index 0000000..e0bf603
--- /dev/null
+++ b/src/share/yang/modules/Makefile.in
@@ -0,0 +1,774 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/share/yang/modules
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(yangmodulesdir)"
+DATA = $(yangmodules_DATA)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = utils
+yangmodulesdir = ${datarootdir}/${PACKAGE_NAME}/yang/modules
+yangmodules_list = ietf-dhcpv6-client@2018-09-04.yang \
+ ietf-interfaces@2018-02-20.yang \
+ ietf-dhcpv6-options@2018-09-04.yang \
+ ietf-dhcpv6-relay@2018-09-04.yang \
+ ietf-dhcpv6-server@2018-09-04.yang \
+ ietf-dhcpv6-types@2018-09-04.yang \
+ ietf-inet-types@2013-07-15.yang \
+ ietf-yang-types@2013-07-15.yang kea-ctrl-agent@2019-08-12.yang \
+ kea-dhcp-ddns@2022-07-27.yang kea-dhcp-types@2022-07-27.yang \
+ kea-dhcp4-server@2022-07-27.yang \
+ kea-dhcp6-server@2022-07-27.yang kea-types@2019-08-12.yang \
+ keatest-module@2018-11-20.yang
+EXTRA_DIST = hashes ${yangmodules_list}
+@HAVE_SYSREPO_TRUE@yangmodules_DATA = ${yangmodules_list}
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/share/yang/modules/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/share/yang/modules/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-yangmodulesDATA: $(yangmodules_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(yangmodules_DATA)'; test -n "$(yangmodulesdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(yangmodulesdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(yangmodulesdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(yangmodulesdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(yangmodulesdir)" || exit $$?; \
+ done
+
+uninstall-yangmodulesDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(yangmodules_DATA)'; test -n "$(yangmodulesdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(yangmodulesdir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(DATA)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(yangmodulesdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-yangmodulesDATA
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-yangmodulesDATA
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip install-yangmodulesDATA installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-yangmodulesDATA
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/share/yang/modules/hashes/ietf-dhcpv6-client@2018-09-04.hash b/src/share/yang/modules/hashes/ietf-dhcpv6-client@2018-09-04.hash
new file mode 100644
index 0000000..ee2a059
--- /dev/null
+++ b/src/share/yang/modules/hashes/ietf-dhcpv6-client@2018-09-04.hash
@@ -0,0 +1 @@
+461ea3c5d65a212413437d52bd328a31591f7cf0b890aec920af7b793deaf749
diff --git a/src/share/yang/modules/hashes/ietf-dhcpv6-options@2018-09-04.hash b/src/share/yang/modules/hashes/ietf-dhcpv6-options@2018-09-04.hash
new file mode 100644
index 0000000..cd99d1b
--- /dev/null
+++ b/src/share/yang/modules/hashes/ietf-dhcpv6-options@2018-09-04.hash
@@ -0,0 +1 @@
+deaf2a9c68b0cf88c6d2080036e50eded5c2356d062f339df44232f5cb240da6
diff --git a/src/share/yang/modules/hashes/ietf-dhcpv6-relay@2018-09-04.hash b/src/share/yang/modules/hashes/ietf-dhcpv6-relay@2018-09-04.hash
new file mode 100644
index 0000000..a6c5446
--- /dev/null
+++ b/src/share/yang/modules/hashes/ietf-dhcpv6-relay@2018-09-04.hash
@@ -0,0 +1 @@
+1a18c64a05cf4295864e2e9871b811b9b4fdf30568f0ce14de045a6af9d04450
diff --git a/src/share/yang/modules/hashes/ietf-dhcpv6-server@2018-09-04.hash b/src/share/yang/modules/hashes/ietf-dhcpv6-server@2018-09-04.hash
new file mode 100644
index 0000000..a6e5569
--- /dev/null
+++ b/src/share/yang/modules/hashes/ietf-dhcpv6-server@2018-09-04.hash
@@ -0,0 +1 @@
+474fcc1cf5a017028796d37240491d72d6a10511539554a709bf877de54c8947
diff --git a/src/share/yang/modules/hashes/ietf-dhcpv6-types@2018-09-04.hash b/src/share/yang/modules/hashes/ietf-dhcpv6-types@2018-09-04.hash
new file mode 100644
index 0000000..9c8977d
--- /dev/null
+++ b/src/share/yang/modules/hashes/ietf-dhcpv6-types@2018-09-04.hash
@@ -0,0 +1 @@
+de34e4338042b62894a42b88026ab2966b98236757b2359167c8f8c238715abe
diff --git a/src/share/yang/modules/hashes/ietf-inet-types@2013-07-15.hash b/src/share/yang/modules/hashes/ietf-inet-types@2013-07-15.hash
new file mode 100644
index 0000000..601b32e
--- /dev/null
+++ b/src/share/yang/modules/hashes/ietf-inet-types@2013-07-15.hash
@@ -0,0 +1 @@
+9ba53b11ecce24a3b878a816555efd8f6559bbcbb5179c62c48af91598744b9e
diff --git a/src/share/yang/modules/hashes/ietf-interfaces@2018-02-20.hash b/src/share/yang/modules/hashes/ietf-interfaces@2018-02-20.hash
new file mode 100644
index 0000000..1445fe1
--- /dev/null
+++ b/src/share/yang/modules/hashes/ietf-interfaces@2018-02-20.hash
@@ -0,0 +1 @@
+990663408339f40c7a68e73920a3d65b18a08eeb87bf788712aec2520aedc475
diff --git a/src/share/yang/modules/hashes/ietf-yang-types@2013-07-15.hash b/src/share/yang/modules/hashes/ietf-yang-types@2013-07-15.hash
new file mode 100644
index 0000000..32ebab2
--- /dev/null
+++ b/src/share/yang/modules/hashes/ietf-yang-types@2013-07-15.hash
@@ -0,0 +1 @@
+0e69553a19dff434f563e5f10fac3dd9b87b5d49d73b92fa11453f9f8e6e2c0b
diff --git a/src/share/yang/modules/hashes/kea-ctrl-agent@2019-08-12.hash b/src/share/yang/modules/hashes/kea-ctrl-agent@2019-08-12.hash
new file mode 100644
index 0000000..bf142b8
--- /dev/null
+++ b/src/share/yang/modules/hashes/kea-ctrl-agent@2019-08-12.hash
@@ -0,0 +1 @@
+5d11568b445edc65a4783f53fe5e8571af9a79b759272d4a3e4bfba994f71087
diff --git a/src/share/yang/modules/hashes/kea-dhcp-ddns@2022-07-27.hash b/src/share/yang/modules/hashes/kea-dhcp-ddns@2022-07-27.hash
new file mode 100644
index 0000000..e0e5205
--- /dev/null
+++ b/src/share/yang/modules/hashes/kea-dhcp-ddns@2022-07-27.hash
@@ -0,0 +1 @@
+5a1ad7031da9ab5f93a87df74ddba26aab2ade6250969c06504a0b5f13007d7f
diff --git a/src/share/yang/modules/hashes/kea-dhcp-types@2022-07-27.hash b/src/share/yang/modules/hashes/kea-dhcp-types@2022-07-27.hash
new file mode 100644
index 0000000..a03e11a
--- /dev/null
+++ b/src/share/yang/modules/hashes/kea-dhcp-types@2022-07-27.hash
@@ -0,0 +1 @@
+37c464015c0ff912febacfd4cff17dd162e73d377df2630a3a3c09f16d812460
diff --git a/src/share/yang/modules/hashes/kea-dhcp4-server@2022-07-27.hash b/src/share/yang/modules/hashes/kea-dhcp4-server@2022-07-27.hash
new file mode 100644
index 0000000..dcd2bbe
--- /dev/null
+++ b/src/share/yang/modules/hashes/kea-dhcp4-server@2022-07-27.hash
@@ -0,0 +1 @@
+9337384d8f47ebbce2e6883d612487820cfd841cab9c1447ed25e8b991c64d81
diff --git a/src/share/yang/modules/hashes/kea-dhcp6-server@2022-07-27.hash b/src/share/yang/modules/hashes/kea-dhcp6-server@2022-07-27.hash
new file mode 100644
index 0000000..a0d37df
--- /dev/null
+++ b/src/share/yang/modules/hashes/kea-dhcp6-server@2022-07-27.hash
@@ -0,0 +1 @@
+9d1ec0093e418675db838a23782bf880971067f68ec1ea2803a61f8656bcf22c
diff --git a/src/share/yang/modules/hashes/kea-types@2019-08-12.hash b/src/share/yang/modules/hashes/kea-types@2019-08-12.hash
new file mode 100644
index 0000000..b0c8859
--- /dev/null
+++ b/src/share/yang/modules/hashes/kea-types@2019-08-12.hash
@@ -0,0 +1 @@
+c06b5242654257c9b8d7f9d2375ab78e9c5cfce74ed4e9f8e21face43a949b9a
diff --git a/src/share/yang/modules/hashes/keatest-module@2018-11-20.hash b/src/share/yang/modules/hashes/keatest-module@2018-11-20.hash
new file mode 100644
index 0000000..95cc911
--- /dev/null
+++ b/src/share/yang/modules/hashes/keatest-module@2018-11-20.hash
@@ -0,0 +1 @@
+f4a46d17ce7791f9d12d0906e1ed8c4b424b29146b5fd5e097663a6d9d388264
diff --git a/src/share/yang/modules/ietf-dhcpv6-client@2018-09-04.yang b/src/share/yang/modules/ietf-dhcpv6-client@2018-09-04.yang
new file mode 100644
index 0000000..c4faa97
--- /dev/null
+++ b/src/share/yang/modules/ietf-dhcpv6-client@2018-09-04.yang
@@ -0,0 +1,346 @@
+module ietf-dhcpv6-client {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-dhcpv6-client";
+ prefix "dhcpv6-client";
+
+ import ietf-dhcpv6-options {
+ prefix dhcpv6-options;
+ }
+ import ietf-dhcpv6-types {
+ prefix dhcpv6-types;
+ }
+ import ietf-interfaces {
+ prefix if;
+ }
+
+ organization "DHC WG";
+ contact
+ "cuiyong@tsinghua.edu.cn
+ wangh13@mails.tsinghua.edu.cn
+ lh.sunlinh@gmail.com
+ ian.farrer@telekom.de
+ sladjana.zechlin@telekom.de
+ hezihao9512@gmail.com ";
+
+ description "This model defines a YANG data model that can be
+ used to configure and manage a DHCPv6 client.";
+
+ revision 2018-09-04 {
+ description "";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2018-03-04 {
+ description "Resolved most issues on the DHC official
+ github";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2017-12-22 {
+ description "Resolve most issues on Ian's github.";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2017-11-24 {
+ description "First version of the separated client specific
+ YANG model.";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+ /*
+ * Data Nodes
+ */
+
+ container client {
+ presence "Enables the client";
+ description "dhcpv6 client portion";
+
+ container client-config {
+ description "configuration tree of client";
+ container duid {
+ description "Sets the DUID";
+ uses dhcpv6-types:duid;
+ }
+ list client-if {
+ key if-name;
+ description "A client may have several interfaces, it is more reasonable to
+ configure and manage parameters on the interface-level. The list defines
+ specific client interfaces and their data. Different interfaces are
+ distinguished by the key which is a configurable string value.";
+ leaf if-name {
+ type if:interface-ref;
+ mandatory true;
+ description "interface name";
+ }
+
+ leaf cli-id {
+ type uint32;
+ mandatory true;
+ description "client id";
+ }
+
+ /*
+ leaf description {
+ type string;
+ description "description of the client interface";
+ }
+ */
+
+ leaf pd-function {
+ type boolean;
+ mandatory true;
+ description "Whether the client can act as a requesting router
+ to request prefixes using prefix delegation ([RFC3633]).";
+ }
+ leaf rapid-commit {
+ type boolean;
+ mandatory true;
+ description "'1' indicates a client can initiate a Solicit-Reply message
+ exchange by adding a Rapid Commit option in Solicit message. '0' means
+ the client is not allowed to add a Rapid Commit option to request
+ addresses in a two-message exchange pattern.";
+ }
+
+ /*
+ container mo-tab {
+ description "The management tab label indicates the operation mode of the
+ DHCPv6 client.
+ 'm'=1 and 'o'=1 indicate the client will use DHCPv6 to obtain all the configuration
+ data.
+ 'm'=1 and 'o'=0 are a meaningless combination.
+ 'm'=0 and 'o'=1 indicate the client will use stateless DHCPv6 to obtain configuration
+ data apart from addresses/prefixes data.
+ 'm'=0 and 'o'=0 represent the client will not use DHCPv6 but use SLAAC to
+ achieve configuration.";
+
+ // if - not sure about the intended use here as it seems
+ // to be redfining what will be received in the PIO. Is
+ // the intention to be whether they PIO options will be
+ // obeyed as received or overridden?
+ leaf m-tab {
+ type boolean;
+ mandatory true;
+ description "m tab";
+ }
+ leaf o-tab {
+ type boolean;
+ mandatory true;
+ description "o tab";
+ }
+ }
+ */
+
+ container client-configured-options {
+ description "client configured options";
+ uses dhcpv6-options:client-option-definitions;
+ }
+ }
+ }
+
+ container client-state {
+ config "false";
+ description "state tree of client";
+ container if-other-paras {
+ description "A client can obtain extra configuration
+ data other than address and prefix information through
+ DHCPv6. This container describes such data the client
+ was configured. The potential configuration data may
+ include DNS server addresses, SIP server domain names, etc.";
+ uses dhcpv6-options:server-option-definitions;
+ }
+ container packet-stats {
+ config "false";
+ description "A container records
+ all the packet status information
+ of a specific interface.";
+ leaf solicit-count {
+ type uint32;
+ mandatory true;
+ description "solicit counter";
+ }
+ leaf request-count {
+ type uint32;
+ mandatory true;
+ description "request counter";
+ }
+ leaf renew-count {
+ type uint32;
+ mandatory true;
+ description "renew counter";
+ }
+ leaf rebind-count {
+ type uint32;
+ mandatory true;
+ description "rebind counter";
+ }
+ leaf decline-count {
+ type uint32;
+ mandatory true;
+ description "decline counter";
+ }
+ leaf release-count {
+ type uint32;
+ mandatory true;
+ description "release counter";
+ }
+ leaf info-req-count {
+ type uint32;
+ mandatory true;
+ description "information request counter";
+ }
+ leaf advertise-count {
+ type uint32;
+ mandatory true;
+ description "advertise counter";
+ }
+ leaf confirm-count {
+ type uint32;
+ mandatory true;
+ description "confirm counter";
+ }
+ leaf reply-count {
+ type uint32;
+ mandatory true;
+ description "reply counter";
+ }
+ leaf reconfigure-count {
+ type uint32;
+ mandatory true;
+ description "recofigure counter";
+ }
+ }
+ }
+
+ }
+
+ /*
+ * Notifications
+ */
+
+ notification notifications {
+ description "dhcpv6 client notification module";
+ container dhcpv6-client-event {
+ description "dhcpv6 client event";
+
+ container ia-lease-event {
+ description "raised when the client was allocated
+ a new IA from the server or it renew/rebind/release
+ its current IA";
+ leaf event-type {
+ type enumeration {
+ enum "allocation" {
+ description "allocate";
+ }
+ enum "rebind" {
+ description "rebind";
+ }
+ enum "renew" {
+ description "renew";
+ }
+ enum "release" {
+ description "release";
+ }
+ }
+ mandatory true;
+ description "event type";
+ }
+ container duid {
+ description "Sets the DUID";
+ uses dhcpv6-types:duid;
+ }
+ leaf iaid {
+ type uint32;
+ mandatory true;
+ description "IAID";
+ }
+ leaf serv-name {
+ type string;
+ description "server name";
+ }
+ leaf description {
+ type string;
+ description "description of event";
+ }
+ }
+
+ container invalid-ia-detected {
+ description "raised when the identity association of the
+ client can be proved to be invalid. Possible condition
+ includes duplicated address, illegal address, etc.";
+ container duid {
+ description "Sets the DUID";
+ uses dhcpv6-types:duid;
+ }
+ leaf cli-duid {
+ type uint32;
+ mandatory true;
+ description "duid of client";
+ }
+ leaf iaid {
+ type uint32;
+ mandatory true;
+ description "IAID";
+ }
+ leaf serv-name {
+ type string;
+ description "server name";
+ }
+ leaf description {
+ type string;
+ description "description of the event";
+ }
+ }
+
+ container retransmission-failed {
+ description "raised when the retransmission mechanism defined
+ in [RFC3315] is failed.";
+ container duid {
+ description "Sets the DUID";
+ uses dhcpv6-types:duid;
+ }
+ leaf description {
+ type enumeration {
+ enum "MRC failed" {
+ description "MRC failed";
+ }
+ enum "MRD failed" {
+ description "MRD failed";
+ }
+ }
+ mandatory true;
+ description "description of failure";
+ }
+ }
+
+ container failed-status-turn-up {
+ description "raised when the client receives a message includes
+ an unsuccessful Status Code option.";
+ container duid {
+ description "Sets the DUID";
+ uses dhcpv6-types:duid;
+ }
+ leaf status-code {
+ type enumeration {
+ enum "1" {
+ description "UnspecFail";
+ }
+ enum "2" {
+ description "NoAddrAvail";
+ }
+ enum "3" {
+ description "NoBinding";
+ }
+ enum "4" {
+ description "NotOnLink";
+ }
+ enum "5" {
+ description "UseMulticast";
+ }
+ }
+ mandatory true;
+ description "employed status code";
+ }
+ }
+
+ }
+ }
+}
diff --git a/src/share/yang/modules/ietf-dhcpv6-options@2018-09-04.yang b/src/share/yang/modules/ietf-dhcpv6-options@2018-09-04.yang
new file mode 100644
index 0000000..76169f1
--- /dev/null
+++ b/src/share/yang/modules/ietf-dhcpv6-options@2018-09-04.yang
@@ -0,0 +1,1391 @@
+module ietf-dhcpv6-options {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-dhcpv6-options";
+ prefix "dhcpv6-options";
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import ietf-yang-types {
+ prefix yang;
+ }
+ import ietf-dhcpv6-types {
+ prefix dhcpv6-types;
+ }
+
+ organization "DHC WG";
+ contact
+ "cuiyong@tsinghua.edu.cn
+ wangh13@mails.tsinghua.edu.cn
+ lh.sunlinh@gmail.com
+ ian.farrer@telekom.de
+ sladjana.zechlin@telekom.de
+ hezihao9512@gmail.com";
+
+ description "This model defines a YANG data model that can be
+ used to configure DHCPv6 options.";
+
+ revision 2018-09-04 {
+ description "";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+ revision 2018-03-04 {
+ description "Resolved most issues on the DHC official
+ github";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2017-12-22 {
+ description "Resolve most issues on Ian's github.";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2017-11-24 {
+ description "First version of the separated DHCPv6 options
+ YANG model.";
+ reference "I-D:draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ /*
+ * Features
+ */
+
+ // features for server options
+ feature server-unicast-op {
+ description "Support for Server Unicast option";
+ }
+ feature sip-server-domain-name-list-op {
+ description "Support for SIP Server Domain Name List option";
+ }
+ feature sip-server-address-list-op {
+ description "Support for SIP Server Address List option";
+ }
+ feature dns-servers-op {
+ description "Support for DNS Servers Option";
+ }
+ feature domain-searchlist-op {
+ description "Support for Domain Search List Option";
+ }
+ feature nis-config-op {
+ description "Support for Network Information Service (NIS)
+ Servers option";
+ }
+ feature nis-plus-config-op {
+ description "Support for Network Information Service V2 (NIS+)
+ Servers option";
+ }
+ feature nis-domain-name-op {
+ description "Support for Network Information Service (NIS)
+ Domain Name option";
+ }
+ feature nis-plus-domain-name-op {
+ description "Support for Network Information Service V2 (NIS+)
+ Server option";
+ }
+ feature sntp-server-op {
+ description "Support for Simple Network Protocol Configuration
+ (SNTP) Servers option";
+ }
+ feature info-refresh-time-op {
+ description "Support for Information Refresh Time option";
+ }
+ feature client-fqdn-op {
+ description "Support for Client FQDN option";
+ }
+ feature posix-timezone-op {
+ description "Support for New POIX Timezone option";
+ }
+ feature tzdb-timezone-op {
+ description "Support for New TZDB Timezone option";
+ }
+ feature ntp-server-op {
+ description "Support for Network Time Protocol (NTP)
+ Server option";
+ }
+ feature boot-file-url-op {
+ description "Support for Boot File URL option";
+ }
+ feature boot-file-param-op {
+ description "Support for Boot File Parameters option";
+ }
+ feature aftr-name-op {
+ description "Support for Address Family Transition
+ Router (AFTR) option";
+ }
+ feature kbr-default-name-op {
+ description "Support for Kerberos Default Name
+ Option";
+ }
+ feature kbr-kdc-op {
+ description "Support for Kerberos KDC option";
+ }
+ feature sol-max-rt-op {
+ description "Support for SOL_MAX_RT option";
+ }
+ feature inf-max-rt-op {
+ description "Support for INF_MAX_RT option";
+ }
+ feature addr-selection-op {
+ description "Support for Address Selection opiton";
+ }
+ feature pcp-server-op {
+ description "Support for Port Control Protocol (PCP)
+ option";
+ }
+ feature s46-rule-op {
+ description "Support for S46 Rule option";
+ }
+ feature s46-br-op {
+ description "Support for S46 Border Relay (BR) option";
+ }
+ feature s46-dmr-op {
+ description "Support for S46 Default Mapping Rule
+ (DMR) option";
+ }
+ feature s46-v4-v6-binding-op {
+ description "Support for S46 IPv4/IPv6 Address
+ Bind option";
+ }
+
+ // features for relay-supplied options
+ feature erp-local-domain-name-op {
+ description "Support for ERP Local Domain Name option";
+ }
+
+ // features for client options
+ feature option-request-op {
+ description "Support for Option Request option";
+ }
+ feature rapid-commit-op {
+ description "Support for Rapid Commit option";
+ }
+ feature user-class-op {
+ description "Support for User Class option";
+ }
+ feature vendor-class-op {
+ description "Support for Vendor Class option";
+ }
+ feature client-arch-type-op {
+ description "Support for Client System Architecture
+ Type option";
+ }
+ feature client-network-interface-identifier-op {
+ description "Support for Client Network Interface
+ Identifier option";
+ }
+ feature kbr-principal-name-op {
+ description "Support for Kerberos Principal
+ Name option";
+ }
+ feature kbr-realm-name-op {
+ description "Support Kerberos Realm Name option";
+ }
+ feature client-link-layer-addr-op {
+ description "Support for Client Link-Layer Address
+ Option";
+ }
+
+ // features for custom options
+ feature operator-op-ipv6-address {
+ description "Support for Option with IPv6 Addresses";
+ }
+ feature operator-op-single-flag {
+ description "Support for Option with Single Flag";
+ }
+ feature operator-op-ipv6-prefix {
+ description "Support for Option with IPv6 Prefix";
+ }
+ feature operator-op-int32 {
+ description "Support for Opion with 32-bit
+ Integer Value";
+ }
+ feature operator-op-int16 {
+ description "Support for Opion with 16-bit Integer Value";
+ }
+ feature operator-op-int8 {
+ description "Support for Opion with 8-bit Integer Value";
+ }
+ feature operator-op-uri {
+ description "Support for Opion with URI";
+ }
+ feature operator-op-textstring {
+ description "Support for Opion with Text String";
+ }
+ feature operator-op-var-data {
+ description "Support for Opion with Variable-Length Data";
+ }
+ feature operator-op-dns-wire {
+ description "Support for Opion with DNS Wire
+ Format Domain Name List";
+ }
+
+ /*
+ * Groupings
+ */
+
+ grouping server-option-definitions {
+ description "Contains definitions for options configured on the
+ DHCPv6 server which will be supplied to clients.";
+
+ container server-unicast-option {
+ if-feature server-unicast-op;
+ presence "Enable this option";
+ description "OPTION_UNICAST (12) Server Unicast Option";
+ reference "RFC3315: Dynamic Host Configuration Protocol for
+ IPv6 (DHCPv6)";
+ leaf server-address {
+ type inet:ipv6-address;
+ description "server ipv6 address";
+ }
+ }
+
+ container sip-server-domain-name-list-option {
+ if-feature sip-server-domain-name-list-op;
+ presence "Enable this option";
+ description "OPTION_SIP_SERVER_D (21) SIP Servers Domain Name List";
+ reference "RFC3319: Dynamic Host Configuration Protocol
+ (DHCPv6) Options for Session Initiation Protocol (SIP) Servers";
+ leaf sip-serv-domain-name {
+ type string;
+ mandatory true;
+ description "sip server domain name";
+ }
+ }
+
+ container sip-server-address-list-option {
+ if-feature sip-server-address-list-op;
+ presence "Enable this option";
+ description "OPTION_SIP_SERVER_A (22) SIP Servers IPv6 Address List";
+ reference "RFC3319: Dynamic Host Configuration Protocol (DHCPv6)
+ Options for Session Initiation Protocol (SIP) Servers";
+ list sip-server {
+ key sip-serv-id;
+ description "sip server info";
+ leaf sip-serv-id {
+ type uint8;
+ mandatory true;
+ description "sip server id";
+ }
+ leaf sip-serv-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description "sip server addr";
+ }
+ }
+ }
+
+ container dns-servers-option {
+ if-feature dns-servers-op;
+ presence "Enable this option";
+ description "OPTION_DNS_SERVERS (23) DNS recursive Name Server option";
+ reference "RFC3646: DNS Configuration options for Dynamic Host Configuration
+ Protocol for IPv6 (DHCPv6)";
+ list dns-server {
+ key dns-serv-id;
+ description "dns server info";
+ leaf dns-serv-id {
+ type uint8;
+ mandatory true;
+ description "DNS server list entry ID.";
+ }
+ leaf dns-serv-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description "DNS server address.";
+ }
+ }
+ }
+
+ container domain-searchlist-option {
+ if-feature domain-searchlist-op;
+ presence "Enable this option";
+ description "OPTION_DOMAIN_LIST (24) Domain Search List Option";
+ reference "RFC3646: DNS Configuration options for Dynamic
+ Host Configuration Protocol for IPv6 (DHCPv6)";
+ list domain-searchlist {
+ key domain-searchlist-id;
+ description "dns server info";
+ leaf domain-searchlist-id {
+ type uint8;
+ mandatory true;
+ description "Domain seachlist entry ID.";
+ }
+ leaf domain-search-list-entry {
+ type string;
+ mandatory true;
+ description "Domain search list entry.";
+ }
+ }
+ }
+ container nis-config-option {
+ if-feature nis-config-op;
+ presence "Enable this option";
+ description "OPTION_NIS_SERVERS (27) Network Information Service (NIS)
+ Servers Option.";
+ reference "RFC3898: Network Information Service (NIS) Configuration
+ Options for Dynamic Host Configuration Protocol for IPv6 (DHCPv6)";
+ list nis-server {
+ key nis-serv-id;
+ description "nis server info";
+ leaf nis-serv-id {
+ type uint8;
+ mandatory true;
+ description "nis server id";
+ }
+ leaf nis-serv-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description "nis server addr";
+ }
+ }
+ }
+
+ container nis-plus-config-option {
+ if-feature nis-plus-config-op;
+ presence "Enable this option";
+ description "OPTION_NISP_SERVERS (28): Network Information Service V2
+ (NIS+) Servers Option.";
+ reference "RFC3989: Network Information Service (NIS) Configuration
+ Options for Dynamic Host Configuration Protocol for IPv6 (DHCPv6)";
+ list nis-plus-server {
+ key nis-plus-serv-id;
+ description "NIS+ server information.";
+ leaf nis-plus-serv-id {
+ type uint8;
+ mandatory true;
+ description "nisp server id";
+ }
+ leaf nis-plus-serv-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description "nisp server addr";
+ }
+ }
+ }
+
+ container nis-domain-name-option {
+ if-feature nis-domain-name-op;
+ presence "Enable this option";
+ description "OPTION_NIS_DOMAIN_NAME (29) Network Information
+ Service (NIS) Domain Name Option";
+ reference "RFC3989: Network Information Service (NIS)
+ Configuration Options for Dynamic Host Configuration Protocol
+ for IPv6 (DHCPv6)";
+ leaf nis-domain-name {
+ type string;
+ description "The Network Information Service (NIS) Domain Name
+ option is used by the server to convey client's NIS Domain Name
+ info to the client.";
+ }
+ }
+
+ container nis-plus-domain-name-option {
+ if-feature nis-plus-domain-name-op;
+ presence "Enable this option";
+ description "OPTION_NISP_DOMAIN_NAME (30) Network Information
+ Service V2 (NIS+) Domain Name Option";
+ reference "RFC3989: Network Information Service (NIS)
+ Configuration Options for Dynamic Host Configuration Protocol
+ for IPv6 (DHCPv6)";
+ leaf nis-plus-domain-name {
+ type string;
+ description "The Network Information Service V2 (NIS+) Domain Name
+ option is used by the server to convey client's NIS+ Domain Name
+ info to the client.";
+ }
+ }
+
+
+ container sntp-server-option {
+ if-feature sntp-server-op;
+ presence "Enable this option";
+ description "OPTION_SNTP_SERVERS (31) Simple Network Time Protocol
+ (SNTP) Servers Option";
+ reference "RFC4075: Simple Network Time Protocol (SNTP) Configuration
+ Option for DHCPv6";
+ list sntp-server {
+ key sntp-serv-id;
+ description "sntp server info";
+ leaf sntp-serv-id {
+ type uint8;
+ mandatory true;
+ description "sntp server id";
+ }
+ leaf sntp-serv-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description "sntp server addr";
+ }
+ }
+ }
+
+ container info-refresh-time-option {
+ if-feature info-refresh-time-op;
+ presence "Enable this option";
+ description "OPTION_INFORMATION_REFRESH_TIME (32) Information Refresh
+ Time option.";
+ reference "RFC4242: Information Refresh Time Option for Dynamic Host
+ Configuration Protocol for IPv6 (DHCPv6";
+ leaf info-refresh-time {
+ type yang:timeticks;
+ mandatory true;
+ description "The refresh time.";
+ }
+ }
+
+ container client-fqdn-option {
+ if-feature client-fqdn-op;
+ presence "Enable this option";
+ description "OPTION_CLIENT_FQDN (39) DHCPv6 Client FQDN Option";
+ reference "RFC4704: The Dynamic Host Configuration Protocol for IPv6
+ (DHCPv6) Client Fully Qualified Domain Name (FQDN) Option";
+ leaf server-initiate-update {
+ type boolean;
+ mandatory true;
+ description "server initiate";
+ }
+ leaf client-initiate-update {
+ type boolean;
+ mandatory true;
+ description "client initiate";
+ }
+ leaf modify-name-from-cli {
+ type boolean;
+ mandatory true;
+ description "modify by client";
+ }
+ }
+
+ container posix-timezone-option {
+ if-feature posix-timezone-op;
+ presence "Enable this option";
+ description "OPTION_NEW_POSIX_TIMEZONE (41) Posix Timezone option";
+ reference "RFC4833: Timezone Options for DHCP";
+ leaf tz-posix {
+ type string;
+ mandatory true;
+ description "TZ Posix IEEE 1003.1 String";
+ }
+ }
+
+ container tzdb-timezone-option {
+ if-feature tzdb-timezone-op;
+ presence "Enable this option";
+ description "OPTION_NEW_TZDB_TIMEZONE (42) Timezone Database option";
+ reference "RFC4822: Timezone Options for DHCP";
+ leaf tz-database {
+ type string;
+ mandatory true;
+ description "Reference to the TZ Database";
+ }
+ }
+
+ container ntp-server-option {
+ //This option looks like it needs work to correctly model the
+ //option as defined in the RFC.
+
+ // Zihao - Re-modelled so it only contains one time source suboption
+
+ if-feature ntp-server-op;
+ presence "Enable this option";
+ description "OPTION_NTP_SERVER (56) NTP Server Option for DHCPv6";
+ reference "RFC5908: Network Time Protocol (NTP) Server Option for
+ DHCPv6";
+ list ntp-server {
+ key ntp-serv-id;
+ description "ntp server info";
+ leaf ntp-serv-id {
+ type uint8;
+ mandatory true;
+ description "NTP server id";
+ }
+ choice ntp-time-source-suboption {
+ description "Select a NTP time source suboption.";
+ case server-address {
+ leaf-list ntp-serv-addr-suboption {
+ type inet:ipv6-address;
+ description "NTP server addr";
+ }
+ }
+ case server-multicast-address {
+ leaf-list ntp-serv-mul-addr-suboption {
+ type inet:ipv6-address;
+ description "NTP server multicast addr";
+ }
+ }
+ case server-fqdn {
+ leaf-list ntp-serv-fqdn-suboption {
+ type string;
+ description "NTP server fqdn";
+ }
+ }
+ }
+ }
+ }
+
+ container boot-file-url-option {
+ if-feature boot-file-url-op;
+ presence "Enable this option";
+ description "OPT_BOOTFILE_URL (59) Boot File URL Option";
+ reference "RFC5970: DHCPv6 Options for Network Boot";
+ list boot-file {
+ key boot-file-id;
+ description "boot file info";
+ leaf boot-file-id {
+ type uint8;
+ mandatory true;
+ description "boot file id";
+ }
+ leaf-list suitable-arch-type {
+ type uint16;
+ description "architecture type";
+ }
+ leaf-list suitable-net-if {
+ type uint32;
+ description "network interface";
+ }
+ leaf boot-file-url {
+ type string;
+ mandatory true;
+ description "url for boot file";
+ }
+ }
+ }
+
+ container boot-file-param-option {
+ if-feature boot-file-param-op;
+ presence "Enable this option";
+ description "OPT_BOOTFiLE_PARAM (60) Boot File Parameters Option";
+ reference "RFC5970: DHCPv6 Options for Network Boot";
+ list boot-file-paras {
+ key para-id;
+ description "boot file parameters";
+ leaf para-id {
+ type uint8;
+ mandatory true;
+ description "parameter id";
+ }
+ leaf parameter {
+ type string;
+ mandatory true;
+ description "parameter value";
+ }
+ }
+ }
+
+ container aftr-name-option {
+ if-feature aftr-name-op;
+ presence "Enable this option";
+ description "OPTION_AFTR_NAME (64) AFTR-Name DHCPv6 Option";
+ reference "RFC6334: Dynamic Host Configuration Protocol for IPv6
+ (DHCPv6) Option for Dual-Stack Lite";
+ leaf tunnel-endpoint-name {
+ type string;
+ mandatory true;
+ description "aftr name";
+ }
+ }
+
+ container kbr-default-name-option {
+ if-feature kbr-default-name-op;
+ presence "Enable this option";
+ description "OPTION_KRB_DEFAULT_REALM_NAME (77) Kerberos Default Realm Name Option";
+ reference "RFC6784: Kerberos Options for DHCPv6";
+ leaf default-realm-name {
+ type string;
+ mandatory true;
+ description "default realm name";
+ }
+ }
+
+ container kbr-kdc-option {
+ if-feature kbr-kdc-op;
+ presence "Enable this option";
+ description "OPTION_KRB_KDC (78) Kerberos KDB Option";
+ reference "RFC6784: Kerberos Options for DHCPv6";
+ list kdc-info {
+ key kdc-id;
+ description "kdc info";
+ leaf kdc-id {
+ type uint8;
+ mandatory true;
+ description "kdc id";
+ }
+ leaf priority {
+ type uint16;
+ mandatory true;
+ description "priority";
+ }
+ leaf weight {
+ type uint16;
+ mandatory true;
+ description "weight";
+ }
+ leaf transport-type {
+ type uint8;
+ mandatory true;
+ description "transport type";
+ }
+ leaf port-number {
+ type uint16;
+ mandatory true;
+ description "port number";
+ }
+ leaf kdc-ipv6-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description "kdc ipv6 addr";
+ }
+ leaf realm-name {
+ type string;
+ mandatory true;
+ description "realm name";
+ }
+ }
+ }
+
+ container sol-max-rt-option {
+ if-feature sol-max-rt-op;
+ presence "Enable this option";
+ description "OPTION_SOL_MAX_RT (82) sol max rt option";
+ reference "RFC7083: Modification to Default Values of
+ SOL_MAX_RT and INF_MAX_RT";
+ leaf sol-max-rt-value {
+ type yang:timeticks;
+ mandatory true;
+ description "sol max rt value";
+ }
+ }
+
+ container inf-max-rt-option {
+ if-feature inf-max-rt-op;
+ presence "Enable this option";
+ description "OPTION_INF_MAX_RT (83) inf max rt option";
+ reference "RFC7083: Modification to Default Values of
+ SOL_MAX_RT and INF_MAX_RT";
+ leaf inf-max-rt-value {
+ type yang:timeticks;
+ mandatory true;
+ description "inf max rt value";
+ }
+ }
+
+ container addr-selection-option {
+ if-feature addr-selection-op;
+ presence "Enable this option";
+ description "OPTION_ADDRSEL (84) and OPTION_ADDRSEL_TABLE (85)";
+ reference "RFC7078: Distributing Address Selection Policy Using
+ DHCPv6";
+ // if - Needs checking to see if this matches the RFC - there
+ // are two options here.
+ // Zihao - I think this matches RFC7078
+ leaf a-bit-set {
+ type boolean;
+ mandatory true;
+ description "a bit";
+ }
+ leaf p-bit-set {
+ type boolean;
+ mandatory true;
+ description "p bit";
+ }
+ list policy-table {
+ key policy-id;
+ description "policy table";
+ leaf policy-id {
+ type uint8;
+ mandatory true;
+ description "policy id";
+ }
+ leaf label {
+ type uint8;
+ mandatory true;
+ description "label";
+ }
+ leaf precedence {
+ type uint8;
+ mandatory true;
+ description "precedence";
+ }
+ leaf prefix-len {
+ type uint8;
+ mandatory true;
+ description "prefix length";
+ }
+ leaf prefix {
+ type inet:ipv6-prefix;
+ mandatory true;
+ description "prefix";
+ }
+ }
+ }
+
+ container pcp-server-option {
+ if-feature pcp-server-op;
+ presence "Enable this option";
+ description "OPTION_V6_PCP_SERVER (86) pcp server option";
+ reference "RFC7291: DHCP Options for the Port Control
+ Protocol (PCP)";
+ list pcp-server {
+ key pcp-serv-id;
+ description "pcp server info";
+ leaf pcp-serv-id {
+ type uint8;
+ mandatory true;
+ description "pcp server id";
+ }
+ leaf pcp-serv-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description "pcp server addr";
+ }
+ }
+ }
+
+ container s46-rule-option {
+ if-feature s46-rule-op;
+ presence "Enable this option";
+ description "OPTION_S46_RULE (89) S46 rule option";
+ reference "RFC7598: DHCPv6 Options for Configuration of
+ Softwire Address and Port-Mapped Clients";
+ list s46-rule {
+ key rule-id;
+ description "s46 rule";
+ leaf rule-id {
+ type uint8;
+ mandatory true;
+ description "rule id";
+ }
+ leaf rule-type {
+ type enumeration {
+ enum "BMR" {
+ description "BMR";
+ }
+ enum "FMR" {
+ description "FMR";
+ }
+ }
+ mandatory true;
+ description "rule type";
+ }
+ leaf prefix4-len {
+ type uint8;
+ mandatory true;
+ description "ipv4 prefix length";
+ }
+ leaf ipv4-prefix {
+ type inet:ipv4-prefix;
+ mandatory true;
+ description "ipv4 prefix";
+ }
+ leaf prefix6-len {
+ type uint8;
+ mandatory true;
+ description "ipv6 prefix length";
+ }
+ leaf ipv6-prefix {
+ type inet:ipv6-prefix;
+ mandatory true;
+ description "ipv6 prefix";
+ }
+ uses dhcpv6-types:portset-para;
+ }
+ }
+
+ container s46-br-option {
+ if-feature s46-br-op;
+ presence "Enable this option";
+ description "OPTION_S46_BR (90) S46 BR Option";
+ reference "RFC7598: DHCPv6 Options for Configuration of
+ Softwire Address and Port-Mapped Clients";
+ list br {
+ key br-id;
+ description "br info";
+ leaf br-id {
+ type uint8;
+ mandatory true;
+ description "br id";
+ }
+ leaf br-ipv6-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description "br ipv6 addr";
+ }
+ }
+ }
+
+ container s46-dmr-option {
+ if-feature s46-dmr-op;
+ presence "Enable this option";
+ description "OPTION_S46_DMR (91) S46 DMR Option";
+ reference "RFC7598: DHCPv6 Options for Configuration of
+ Softwire Address and Port-Mapped Clients";
+ list dmr {
+ key dmr-id;
+ description "dmr info";
+ leaf dmr-id {
+ type uint8;
+ mandatory true;
+ description "dmr id";
+ }
+ leaf dmr-prefix-len {
+ type uint8;
+ mandatory true;
+ description "dmr prefix length";
+ }
+ leaf dmr-ipv6-prefix {
+ type inet:ipv6-prefix;
+ mandatory true;
+ description "dmr ipv6 prefix";
+ }
+ }
+ }
+
+ container s46-v4-v6-binding-option {
+ if-feature s46-v4-v6-binding-op;
+ presence "Enable this option";
+ description "OPTION_S46_V4V6BIND (92) S46 IPv4/IPv6 Address
+ Binding option";
+ reference "RFC7598: DHCPv6 Options for Configuration of
+ Softwire Address and Port-Mapped Clients";
+ list ce {
+ key ce-id;
+ description "ce info";
+ leaf ce-id {
+ type uint8;
+ mandatory true;
+ description "ce id";
+ }
+ leaf ipv4-addr {
+ type inet:ipv4-address;
+ mandatory true;
+ description "ce ipv4 addr";
+ }
+ leaf bind-prefix6-len {
+ type uint8;
+ mandatory true;
+ description "bind ipv6 prefix
+ length";
+ }
+ leaf bind-ipv6-prefix {
+ type inet:ipv6-address;
+ mandatory true;
+ description "bind ipv6 prefix";
+ }
+ uses dhcpv6-types:portset-para;
+ }
+ }
+
+ }
+ //if - NB - The list of options needs to be updated.
+
+
+ grouping relay-supplied-option-definitions {
+ // if - The structure here needs to be checked and probably reworked.
+ description "OPTION_RSOO (66) Relay-Supplied Options option";
+ reference "RFC6422: Relay-Supplied DHCP Options";
+ container erp-local-domain-name-option {
+ if-feature erp-local-domain-name-op;
+ presence "Enable this option";
+ description "OPTION_ERP_LOCAL_DOMAIN_NAME (65) DHCPv6 ERP Local
+ Domain Name Option";
+ reference "RFC6440: The EAP Re-authentication Protocol (ERP)
+ Local Domain Name DHCPv6 Option";
+ list erp-for-client {
+ key cli-id;
+ description "erp for client";
+ leaf cli-id {
+ type uint32;
+ mandatory true;
+ description "client id";
+ }
+ container duid {
+ description "Sets the DUID";
+ // uses duid;
+ // if - Maybe DUID definition needs to be moved to this module.
+ uses dhcpv6-types:duid;
+ }
+ leaf erp-name {
+ type string;
+ mandatory true;
+ description "erp name";
+ }
+ }
+ }
+ }
+
+ grouping client-option-definitions {
+ description "Contains definitions for options configured on the
+ DHCPv6 client which will be sent to the server.";
+
+ list new-or-standard-cli-option {
+ key option-code;
+ description "new or standard client option";
+ leaf option-code {
+ type uint16;
+ mandatory true;
+ description "option code";
+ }
+ leaf option-name {
+ type string;
+ mandatory true;
+ description "option name";
+ }
+ leaf option-description {
+ type string;
+ mandatory true;
+ description "description of client
+ option";
+ }
+ leaf option-reference {
+ type string;
+ description "the reference of option";
+ }
+ leaf option-value {
+ type string;
+ mandatory true;
+ description "the option value";
+ }
+ }
+
+ container option-request-option {
+ if-feature option-request-op;
+ presence "Enable this option";
+ description "OPTION_ORO (6) Option Request Option";
+ reference "RFC3315: Dynamic Host Configuration Protocol for
+ IPv6 (DHCPv6)";
+ list oro-option {
+ key option-code;
+ description "oro option";
+ leaf option-code {
+ type uint16;
+ mandatory true;
+ description "option code";
+ }
+ leaf description {
+ type string;
+ mandatory true;
+ description "description of oro
+ options";
+ }
+ }
+ }
+
+ container user-class-option {
+ if-feature user-class-op;
+ presence "Enable this option";
+ description "OPTION_USER_CLASS (15) User Class Option";
+ reference "RFC3315: Dynamic Host Configuration Protocol
+ for IPv6 (DHCPv6)";
+ list user-class {
+ key user-class-id;
+ description "user class";
+ leaf user-class-id {
+ type uint8;
+ mandatory true;
+ description "user class id";
+ }
+ leaf user-class-data {
+ type string;
+ mandatory true;
+ description "The information contained in the data area
+ of this option is contained in one or more opaque
+ fields that represent the user class or classes of
+ which the client is a member. ";
+ }
+ }
+ }
+
+ container vendor-class-option {
+ if-feature vendor-class-op;
+ presence "Enable this option";
+ description "OPTION_VENDOR_CLASS (16) Vendor Class Option";
+ reference "RFC3315: Dynamic Host Configuration Protocol
+ for IPv6 (DHCPv6)";
+ leaf enterprise-number {
+ type uint32;
+ mandatory true;
+ description "enterprise number";
+ }
+ list vendor-class {
+ key vendor-class-id;
+ description "vendor class";
+ leaf vendor-class-id {
+ type uint8;
+ mandatory true;
+ description "vendor class id";
+ }
+ leaf vendor-class-data {
+ type string;
+ mandatory true;
+ description "The vendor-class-data is composed of a series of
+ separate items, each of which describes some characteristic
+ of the client's hardware configuration. Examples of
+ vendor-class-data instances might include the version of the
+ operating system the client is running or the amount of memory
+ installed on the client.";
+ }
+ }
+ }
+
+ container client-fqdn-option {
+ if-feature client-fqdn-op;
+ presence "Enable this option";
+ description "OPTION_CLIENT_FQDN (39) The Dynamic Host
+ Configuration Protocol for IPv6 (DHCPv6) Client Fully
+ Qualified Domain Name (FQDN) Option";
+ reference "RFC4704: The Dynamic Host Configuration Protocol
+ for IPv6 (DHCPv6) Client Fully Qualified Domain Name (FQDN)
+ Option";
+ leaf fqdn {
+ type string;
+ mandatory true;
+ description "fqdn";
+ }
+ leaf server-initiate-update {
+ type boolean;
+ mandatory true;
+ description "whether server initiate";
+ }
+ leaf client-initiate-update {
+ type boolean;
+ mandatory true;
+ description "whether client initiate";
+ }
+ }
+
+ container client-arch-type-option {
+ if-feature client-arch-type-op;
+ presence "Enable this option";
+ description "OPTION_CLIENT_ARCH_TYPE (61) Client System
+ Architecture Type Option";
+ reference "RFC5970: DHCPv6 Options for Network Boot";
+ list architecture-types {
+ key type-id;
+ description "architecture types";
+ leaf type-id {
+ type uint16;
+ mandatory true;
+ description "type id";
+ }
+ leaf most-preferred {
+ type boolean;
+ mandatory true;
+ description "most preferred flag";
+ }
+ }
+ }
+
+ container client-network-interface-identifier-option {
+ if-feature client-network-interface-identifier-op;
+ presence "Enable this option";
+ description "OPTION_NII (62) Client Network Interface
+ Identifier Option";
+ reference "RFC5970: DHCPv6 Options for Network Boot";
+ leaf type {
+ type uint8;
+ mandatory true;
+ description "type";
+ }
+ leaf major {
+ type uint8;
+ mandatory true;
+ description "major";
+ }
+ leaf minor {
+ type uint8;
+ mandatory true;
+ description "minor";
+ }
+ }
+
+ container kbr-principal-name-option {
+ if-feature kbr-principal-name-op;
+ presence "Enable this option";
+ description "OPTION_KRB_PRINCIPAL_NAME (75) Kerberos
+ Principal Name Option";
+ reference "RFC6784: Kerberos Options for DHCPv6";
+ list principle-name {
+ key principle-name-id;
+ description "principle name";
+ leaf principle-name-id {
+ type uint8;
+ mandatory true;
+ description "principle name id";
+ }
+ leaf name-type {
+ type int32;
+ mandatory true;
+ description "This field specifies the type of name that follows.";
+ }
+ leaf name-string {
+ type string;
+ mandatory true;
+ description "This field encodes a sequence of components that form
+ a name, each component encoded as a KerberoString";
+ }
+ }
+ }
+
+ container kbr-realm-name-option {
+ if-feature kbr-realm-name-op;
+ presence "Enable this option";
+ description "OPTION_KRB_REALM_NAME (76) Kerberos Realm Name Option";
+ reference "RFC6784: Kerberos Options for DHCPv6";
+ leaf realm-name {
+ type string;
+ mandatory true;
+ description "realm name";
+ }
+ }
+
+ container client-link-layer-addr-option {
+ if-feature client-link-layer-addr-op;
+ presence "Enable this option";
+ description "OPTION_CLIENT_LINKLAYER_ADDR (79) DHCPv6 Client
+ Link-Layer Address Option";
+ reference "RFC6939: Client Link-Layer Address Option in
+ DHCPv6";
+ leaf link-layer-type {
+ type uint16;
+ mandatory true;
+ description "Client link-layer address type. The link-layer
+ type MUST be a valid hardware type assigned by the IANA,
+ as described in [RFC0826]";
+ }
+ leaf link-layer-addr {
+ type string;
+ mandatory true;
+ description "Client link-layer address";
+ }
+ }
+
+ }
+
+
+ grouping custom-option-definitions {
+ description "operator customized options";
+
+ container operator-option-ipv6-address {
+ if-feature operator-op-ipv6-address;
+ presence "Enable this option";
+ description "operator ipv6 address option";
+ reference "RFC7227: Guidelines for Creating New DHCPv6 Options";
+ list operator-ipv6-addr {
+ key operator-ipv6-addr-id;
+ description "operator ipv6 address info";
+ leaf operator-ipv6-addr-id {
+ type uint8;
+ mandatory true;
+ description "operator ipv6 address id";
+ }
+ leaf operator-ipv6-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description "operator ipv6 address id";
+ }
+ }
+ }
+
+ container operator-option-single-flag {
+ if-feature operator-op-single-flag;
+ presence "Enable this option";
+ description "operator single flag";
+ reference "RFC7227: Guidelines for Creating New DHCPv6
+ Options";
+ list flag {
+ key flag-id;
+ description "operator single flag info";
+ leaf flag-id {
+ type uint8;
+ mandatory true;
+ description "operator single flag id";
+ }
+ leaf flag-value{
+ type boolean;
+ mandatory true;
+ description "operator single flag value";
+ }
+ }
+ }
+
+ container operator-option-ipv6-prefix {
+ if-feature operator-op-ipv6-prefix;
+ presence "Enable this option";
+ description "operator ipv6 prefix option";
+ reference "RFC7227: Guidelines for Creating New DHCPv6
+ Options";
+ list operator-ipv6-prefix {
+ key operator-ipv6-prefix-id;
+ description "operator ipv6 prefix info";
+ leaf operator-ipv6-prefix-id {
+ type uint8;
+ mandatory true;
+ description "operator ipv6 prefix id";
+ }
+ leaf operator-ipv6-prefix6-len {
+ type uint8;
+ mandatory true;
+ description "operator ipv6 prefix length";
+ }
+ leaf operator-ipv6-prefix {
+ type inet:ipv6-prefix;
+ mandatory true;
+ description "operator ipv6 prefix";
+ }
+ }
+ }
+
+ container operator-option-int32 {
+ if-feature operator-op-int32;
+ presence "Enable this option";
+ description "operator integer 32 option";
+ reference "RFC7227: Guidelines for Creating New DHCPv6
+ Options";
+ list int32val {
+ key int32val-id;
+ description "operator integer 32 info";
+ leaf int32val-id {
+ type uint8;
+ mandatory true;
+ description "operator integer 32 id";
+ }
+ leaf int32val {
+ type uint32;
+ mandatory true;
+ description "operator integer 32 value";
+ }
+ }
+ }
+
+ container operator-option-int16 {
+ if-feature operator-op-int16;
+ presence "Enable this option";
+ description "operator integer 16 option";
+ reference "RFC7227: Guidelines for Creating New DHCPv6
+ Options";
+ list int16val {
+ key int16val-id;
+ description "operator integer 16 info";
+ leaf int16val-id {
+ type uint8;
+ mandatory true;
+ description "operator integer 16 id";
+ }
+ leaf int16val {
+ type uint16;
+ mandatory true;
+ description "operator integer 16 value";
+ }
+ }
+ }
+
+ container operator-option-int8 {
+ if-feature operator-op-int8;
+ presence "Enable this option";
+ description "operator integer 8 option";
+ reference "RFC7227: Guidelines for Creating New DHCPv6
+ Options";
+ list int8val {
+ key int8val-id;
+ description "operator integer 8 info";
+ leaf int8val-id {
+ type uint8;
+ mandatory true;
+ description "operator integer 8 id";
+ }
+ leaf int8val {
+ type uint8;
+ mandatory true;
+ description "operator integer 8 value";
+ }
+ }
+ }
+
+ container operator-option-uri {
+ if-feature operator-op-uri;
+ presence "Enable this option";
+ description "operator uri option";
+ reference "RFC7227: Guidelines for Creating New DHCPv6 Options";
+ list uri{
+ key uri-id;
+ description "operator uri info";
+ leaf uri-id {
+ type uint8;
+ mandatory true;
+ description "operator uri id";
+ }
+ leaf uri {
+ type string;
+ mandatory true;
+ description "operator uri value";
+ }
+ }
+ }
+
+ container operator-option-textstring {
+ if-feature operator-op-textstring;
+ presence "Enable this option";
+ description "operator itext string option";
+ reference "RFC7227: Guidelines for Creating New DHCPv6 Options";
+ list textstring{
+ key textstring-id;
+ description "operator text string info";
+ leaf textstring-id {
+ type uint8;
+ mandatory true;
+ description "operator text string id";
+ }
+ leaf textstring {
+ type string;
+ mandatory true;
+ description "operator text string value";
+ }
+ }
+ }
+
+ container operator-option-var-data {
+ if-feature operator-op-var-data;
+ presence "Enable this option";
+ description "operator variable length data option";
+ reference "RFC7227: Guidelines for Creating New DHCPv6 Options";
+ list int32val {
+ key var-data-id;
+ description "operator ivariable length data info";
+ leaf var-data-id {
+ type uint8;
+ mandatory true;
+ description "operator variable length id";
+ }
+ leaf var-data {
+ type binary;
+ mandatory true;
+ description "operator variable length value";
+ }
+ }
+ }
+
+ container operator-option-dns-wire {
+ if-feature operator-op-dns-wire;
+ presence "Enable this option";
+ description "operator dns wire format domain name list option";
+ reference "RFC7227: Guidelines for Creating New DHCPv6
+ Options";
+ list operator-option-dns-wire {
+ key operator-option-dns-wire-id;
+ description "operator dns wire format info";
+ leaf operator-option-dns-wire-id {
+ type uint8;
+ mandatory true;
+ description "operator dns wire format id";
+ }
+ leaf operator-option-dns-wire{
+ type binary;
+ mandatory true;
+ description "operator dns wire format value";
+ }
+ }
+ }
+
+ }
+}
diff --git a/src/share/yang/modules/ietf-dhcpv6-relay@2018-09-04.yang b/src/share/yang/modules/ietf-dhcpv6-relay@2018-09-04.yang
new file mode 100644
index 0000000..b480ff4
--- /dev/null
+++ b/src/share/yang/modules/ietf-dhcpv6-relay@2018-09-04.yang
@@ -0,0 +1,476 @@
+module ietf-dhcpv6-relay {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-dhcpv6-relay";
+ prefix "dhcpv6-relay";
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import ietf-dhcpv6-options {
+ prefix dhcpv6-options;
+ }
+ import ietf-dhcpv6-types {
+ prefix dhcpv6-types;
+ }
+ import ietf-interfaces {
+ prefix if;
+ }
+
+ organization
+ "IETF DHC (Dynamic Host Configuration) Working group";
+
+ contact
+ "cuiyong@tsinghua.edu.cn
+ lh.sunlinh@gmail.com
+ ian.farrer@telekom.de
+ sladjana.zechlin@telekom.de
+ hezihao9512@gmail.com";
+
+ description
+ "This model defines a YANG data model that can be
+ used to configure and manage a DHCPv6 relay.";
+
+ revision 2018-09-04 {
+ description "";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2018-03-04 {
+ description "Resolved most issues on the DHC official
+ github";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2017-12-22 {
+ description
+ "Resolve most issues on Ian's github.";
+ reference
+ "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2017-11-24 {
+ description
+ "First version of the separated relay specific
+ YANG model.";
+ reference
+ "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ /*
+ * Data Nodes
+ */
+
+ container relay {
+ presence
+ "Enables the relay";
+ description
+ "DHCPv6 relay portion";
+
+ container relay-config {
+ description
+ "This container contains the configuration data
+ of the relay.";
+ container relay-attributes {
+ description
+ "A container describes some basic attributes of the relay
+ agent including some relay agent specific options data that
+ need to be configured previously.
+ Such options include Remote-Id option and Subscriber-Id
+ option.";
+ leaf name {
+ type string;
+ description
+ "Relay agent name";
+ }
+ leaf description {
+ type string;
+ description
+ "Textual description of the relay agent";
+ }
+ leaf-list dest-addrs {
+ type inet:ipv6-address;
+ description
+ "Each DHCPv6 relay agent may be configured with a list
+ of destination addresses.
+ This node defines such a list of IPv6 addresses that
+ may include unicast addresses, multicast addresses or
+ other addresses.";
+ }
+ list subscribers {
+ key subscriber;
+ description
+ "Subscribers";
+ leaf subscriber {
+ type uint8;
+ mandatory true;
+ description
+ "Subscriber";
+ }
+ leaf subscriber-id {
+ type string;
+ mandatory true;
+ description
+ "Subscriber id";
+ }
+ }
+ list remote-host {
+ key ent-num;
+ description
+ "Remote host";
+ leaf ent-num {
+ type uint32;
+ mandatory true;
+ description
+ "Enterprise number";
+ }
+ leaf remote-id {
+ type string;
+ mandatory true;
+ description
+ "Remote id";
+ }
+ }
+ uses dhcpv6-types:vendor-infor;
+ }
+
+ container rsoo-option-sets {
+ description
+ "DHCPv6 relay agent could provide some information that would
+ be useful to DHCPv6 client.
+ Since relay agent cannot provide options directly to the
+ client, RSOO-enabled options are defined to propose options
+ for the server to send to the client.
+ This container models such RSOO-enabled options.";
+ reference
+ "RFC6422";
+ list option-set {
+ key option-set-id;
+ description
+ "This list under the 'rsoo-option-sets' container is similar
+ to the that defined in server module.
+ It allows the relay to implement several sets of RSOO-enabled
+ options for different interfaces.
+ The list only includes the EAP Re-authentication Protocol
+ (ERP) Local Domain Name DHCPv6 Option defined in RFC6440,
+ since it is the only one RSOO-enabled options accepted by
+ IANA so far.";
+ leaf option-set-id {
+ type uint32;
+ description "Option sed id";
+ }
+ uses dhcpv6-options:relay-supplied-option-definitions;
+ }
+ }
+
+ list relay-if {
+ // if - This should reference an entry in ietf-interfaces
+ key if-name;
+ description
+ "A relay agent may have several interfaces, we should provide
+ a way to configure and manage parameters on the interface-level.
+ A list that describes specific interfaces and their corresponding
+ parameters is employed to fulfil the configfuration. Here we use
+ a string called 'if-name' as the key of list.";
+ leaf if-name {
+ type if:interface-ref;
+ mandatory true;
+ description
+ "Interface name";
+ }
+ leaf interface-id {
+ type string;
+ description
+ "Interface id";
+ }
+
+ /*
+ leaf enable {
+ type boolean;
+ mandatory true;
+ description "whether this interface is enabled";
+ }
+ */
+
+ leaf ipv6-address {
+ type inet:ipv6-address;
+ description
+ "IPv6 address for this interface";
+ }
+
+ leaf rsoo-option-set-id {
+ type leafref {
+ path "/relay/relay-config/rsoo-option-sets/option-set/option-set-id";
+ }
+ description "Configured Relay Supplied Option set";
+ }
+
+ list next-entity {
+ key dest-addr;
+ description
+ "This node defines a list that is used to describe the
+ next hop entity of this relay distinguished by their
+ addresses.";
+ leaf dest-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description
+ "Destination addr";
+ }
+ leaf available {
+ type boolean;
+ mandatory true;
+ description
+ "Whether the next entity is available or not";
+ }
+ leaf multicast {
+ type boolean;
+ mandatory true;
+ description
+ "Whether the address is multicast or not";
+ }
+ leaf server {
+ type boolean;
+ mandatory true;
+ description
+ "Whether the next entity is a server";
+ }
+ }
+ }
+ }
+
+
+ container relay-state {
+ config "false";
+ description
+ "State data of relay";
+ list relay-if {
+ key if-name;
+ description
+ "A relay agent may have several interfaces, we should provide
+ a way to configure and manage parameters on the interface-level.
+ A list that describes specific interfaces and their corresponding
+ parameters is employed to fulfil the configfuration. Here we use
+ a string called 'if-name' as the key of list.";
+ leaf if-name{
+ type string;
+ mandatory true;
+ description
+ "Interface name";
+ }
+ list pd-route {
+ // if - need to look at if/how we model these. If they are
+ // going to be modelled, then they should be ro state
+ // entries (we're not trying to configure routes here)
+ key pd-route-id;
+ description "pd route";
+ leaf pd-route-id {
+ type uint8;
+ mandatory true;
+ description
+ "PD route id";
+ }
+ leaf requesting-router-id {
+ type uint32;
+ mandatory true;
+ description
+ "Requesting router id";
+ }
+ leaf delegating-router-id {
+ type uint32;
+ mandatory true;
+ description
+ "Delegating router id";
+ }
+ leaf next-router {
+ type inet:ipv6-address;
+ mandatory true;
+ description
+ "Next router";
+ }
+ leaf last-router {
+ type inet:ipv6-address;
+ mandatory true;
+ description
+ "Previous router";
+ }
+ }
+ list next-entity {
+ key dest-addr;
+ description "This node defines a list that is used to
+ describe the next hop entity of this relay agent.
+ Different entities are distinguished by their
+ addresses.";
+ leaf dest-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description "destination addr";
+ }
+ container packet-stats {
+ description "packet statistics";
+ leaf solicit-rvd-count {
+ type uint32;
+ mandatory true;
+ description "solicit received counter";
+ }
+ leaf request-rvd-count {
+ type uint32;
+ mandatory true;
+ description "request received counter";
+ }
+ leaf renew-rvd-count {
+ type uint32;
+ mandatory true;
+ description "renew received counter";
+ }
+ leaf rebind-rvd-count {
+ type uint32;
+ mandatory true;
+ description "rebind recevied counter";
+ }
+ leaf decline-rvd-count {
+ type uint32;
+ mandatory true;
+ description "decline received counter";
+ }
+ leaf release-rvd-count {
+ type uint32;
+ mandatory true;
+ description "release received counter";
+ }
+ leaf info-req-rvd-count {
+ type uint32;
+ mandatory true;
+ description "information request counter";
+ }
+ leaf relay-for-rvd-count {
+ type uint32;
+ mandatory true;
+ description "relay forward received counter";
+ }
+ leaf relay-rep-rvd-count {
+ type uint32;
+ mandatory true;
+ description "relay reply received counter";
+ }
+ leaf packet-to-cli-count {
+ type uint32;
+ mandatory true;
+ description "packet to client counter";
+ }
+ leaf adver-sent-count {
+ type uint32;
+ mandatory true;
+ description "advertisement sent counter";
+ }
+ leaf confirm-sent-count {
+ type uint32;
+ mandatory true;
+ description "confirm sent counter";
+ }
+ leaf reply-sent-count {
+ type uint32;
+ mandatory true;
+ description "reply sent counter";
+ }
+ leaf reconfig-sent-count {
+ type uint32;
+ mandatory true;
+ description "reconfigure sent counter";
+ }
+ leaf relay-for-sent-count {
+ type uint32;
+ mandatory true;
+ description "relay forward sent counter";
+ }
+ leaf relay-rep-sent-count {
+ type uint32;
+ mandatory true;
+ description "relay reply sent counter";
+ }
+ }
+ }
+ }
+ container relay-stats {
+ config "false";
+ description
+ "Relay statistics";
+ leaf cli-packet-rvd-count {
+ type uint32;
+ mandatory true;
+ description
+ "Client packet received counter";
+ }
+ leaf relay-for-rvd-count {
+ type uint32;
+ mandatory true;
+ description
+ "Relay forward received counter";
+ }
+ leaf relay-rep-rvd-count {
+ type uint32;
+ mandatory true;
+ description
+ "Relay reply recevied counter";
+ }
+ leaf packet-to-cli-count {
+ type uint32;
+ mandatory true;
+ description
+ "Packet to client counter";
+ }
+ leaf relay-for-sent-count {
+ type uint32;
+ mandatory true;
+ description
+ "Relay forward sent counter";
+ }
+ leaf relay-rep-sent-count {
+ type uint32;
+ mandatory true;
+ description
+ "Relay reply sent counter";
+ }
+ leaf discarded-packet-count {
+ type uint32;
+ mandatory true;
+ description
+ "Discarded packet counter";
+ }
+ }
+ }
+ }
+
+ /*
+ * Notifications
+ */
+
+ notification notifications {
+ description "DHCPv6 relay notification module";
+ container dhcpv6-relay-event {
+ description
+ "DHCPv6 relay event";
+ container topo-changed {
+ description
+ "Raised when the topology of the relay agent is changed.";
+ leaf relay-if-name {
+ type string;
+ mandatory true;
+ description
+ "Relay interface name";
+ }
+ leaf first-hop {
+ type boolean;
+ mandatory true;
+ description
+ "First hop";
+ }
+ leaf last-entity-addr {
+ type inet:ipv6-address;
+ mandatory true;
+ description
+ "Last entity address";
+ }
+ }
+ }
+ }
+}
diff --git a/src/share/yang/modules/ietf-dhcpv6-server@2018-09-04.yang b/src/share/yang/modules/ietf-dhcpv6-server@2018-09-04.yang
new file mode 100644
index 0000000..0e13df4
--- /dev/null
+++ b/src/share/yang/modules/ietf-dhcpv6-server@2018-09-04.yang
@@ -0,0 +1,967 @@
+module ietf-dhcpv6-server {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-dhcpv6-server";
+ prefix "dhcpv6-server";
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import ietf-yang-types {
+ prefix yang;
+ }
+ import ietf-dhcpv6-options {
+ prefix dhcpv6-options;
+ }
+ import ietf-dhcpv6-types {
+ prefix dhcpv6-types;
+ }
+ import ietf-interfaces {
+ prefix if;
+ }
+
+ organization "DHC WG";
+ contact
+ "cuiyong@tsinghua.edu.cn
+ lh.sunlinh@gmail.com
+ ian.farrer@telekom.de
+ sladjana.zechlin@telekom.de
+ hezihao9512@gmail.com";
+
+ description "This model defines a YANG data model that can be
+ used to configure and manage a DHCPv6 server.";
+
+ revision 2018-09-04 {
+ description "";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2018-03-04 {
+ description "Resolved most issues on the DHC official
+ github";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2017-12-22 {
+ description "Resolve most issues on Ian's github.";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2017-11-24 {
+ description "First version of the separated server specific
+ YANG model.";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ /*
+ * Typedef
+ */
+ typedef threshold {
+ type union {
+ type uint16 {
+ range 0..100;
+ }
+ type enumeration {
+ enum "disabled" {
+ description "No threshold";
+ }
+ }
+ }
+ description "Threshold value in percent";
+ }
+
+ /*
+ * Data Nodes
+ */
+ container server {
+ presence "Enables the server";
+ description "DHCPv6 server portion";
+
+ /*
+ * Configuration data
+ */
+ container server-config {
+ description "This container contains the configuration data
+ of a server.";
+ container serv-attributes {
+ description
+ "This container contains basic attributes of a DHCPv6 server
+ such as IPv6 address, server name and so on. Some optional
+ functions that can be provided by the server is also included.";
+ container duid {
+ description "Sets the DUID of server";
+ uses dhcpv6-types:duid;
+ }
+ leaf name {
+ type string;
+ description "server's name";
+ }
+ leaf description {
+ type string;
+ description "description of the server.";
+ }
+ leaf-list ipv6-address {
+ type inet:ipv6-address;
+ description "server's IPv6 address.";
+ }
+ leaf-list interfaces-config {
+ // Note - this should probably be references to
+ // entries in the ietf-interfaces model
+ type if:interface-ref;
+ description "A leaf list to denote which one or more interfaces
+ the server should listen on. The default value is to listen
+ on all the interfaces. This node is also used to set a unicast
+ address for the server to listen with a specific interface.
+ For example, if people want the server to listen on a unicast
+ address with a specific interface, he can use the format like
+ 'eth1/2001:db8::1'.";
+ }
+ container lease-storage {
+ description "Indicates how the server stores the lease";
+ choice storage-type {
+ description "the type of lease storage";
+ // leaf persist {
+ // type boolean;
+ // mandatory true;
+ // description "controls whether the new leases and updates to existing leases are
+ // written to the file";
+ // }
+ case memfile {
+ description "the server stores lease information in a CSV file";
+
+ leaf memfile-name {
+ type string;
+ description "specifies an absolute location of the lease file in which new leases
+ and lease updates will be recorded";
+ }
+
+ leaf memfile-lfc-interval {
+ type uint64;
+ description "specifies the interval in seconds, at which the server will perform a
+ lease file cleanup (LFC)";
+ }
+
+ }
+ case mysql {
+ leaf mysql-name {
+ type string;
+ description "type of the database";
+ }
+ leaf mysql-host {
+ type string;
+ description "If the database is located
+ on a different system to the DHCPv6 server, the database host name must also be specified.";
+ }
+ leaf mysql-password {
+ type string;
+ description "the credentials of the account under which the server will access the database";
+ }
+ leaf mysql-port {
+ type uint8;
+ description "If the database is located on a different system, the port number may be specified";
+ }
+ leaf mysql-lfc-interval {
+ type uint64;
+ description "specifies the interval in seconds, at which the server will perform a
+ lease file cleanup (LFC)";
+ }
+ leaf mysql-connect-timeout {
+ type uint64;
+ description "If the database is located on a different system, a longer interval needs to be specified";
+ }
+
+ }
+ case postgresql {
+ leaf postgresql-name {
+ type string;
+ description "type of the database";
+ }
+ leaf postgresql-host {
+ type string;
+ description "If the database is located
+ on a different system to the DHCPv6 server, the database host name must also be specified.";
+ }
+ leaf postgresql-password {
+ type string;
+ description "the credentials of the account under which the server will access the database";
+ }
+ leaf postgresql-port {
+ type uint8;
+ description "If the database is located on a different system, the port number may be specified";
+ }
+ leaf postgresql-lfc-interval {
+ type uint64;
+ description "specifies the interval in seconds, at which the server will perform a
+ lease file cleanup (LFC)";
+ }
+ leaf postgresql-connect-timeout {
+ type uint64;
+ description "If the database is located on a different system, a longer interval needs to be specified";
+ }
+ }
+ case cassandra {
+ leaf cassandra-name {
+ type string;
+ description "type of the database";
+ }
+ leaf cassandra-contact-points {
+ type string;
+ description "Cassandra takes a list of comma separated IP addresses to contact the cluster";
+ }
+ leaf cassandra-password {
+ type string;
+ description "the credentials of the account under which the server will access the database";
+ }
+ leaf cassandra-lfc-interval {
+ type uint64;
+ description "specifies the interval in seconds, at which the server will perform a
+ lease file cleanup (LFC)";
+ }
+ leaf cassandra-connect-timeout {
+ type uint64;
+ description "If the database is located on a different system, a longer interval needs to be specified";
+ }
+ }
+ }
+ }
+ uses dhcpv6-types:vendor-infor;
+ }
+
+ container option-sets {
+ description "DHCPv6 employs various options to carry additional
+ information and parameters in DHCP messages. This container defines
+ all the possible options that need to be configured at the server
+ side. ";
+ list option-set {
+ key option-set-id;
+ description "A server may allow different option sets to be
+ configured for different conditions (i.e. different networks,
+ clients and etc). This 'option-set' list enables various sets of
+ options being defined and configured in a single server. Different
+ sets are distinguished by the key called 'option-set-id'. All the
+ possible options discussed above are defined in the list and each
+ option is corresponding to a container. Since all the options in
+ the list are optional, each container in this list has a 'presence'
+ statement to indicate whether this option (container) will be
+ included in the current option set or not. In addition, each container
+ also has a 'if-feature' statement to indicate whether the server
+ supports this option (container).";
+ leaf option-set-id {
+ type uint32;
+ description "option set id";
+ }
+ uses dhcpv6-options:server-option-definitions;
+ uses dhcpv6-options:custom-option-definitions;
+ }
+ }
+
+ container network-ranges {
+ description "This model supports a hierarchy
+ to achieve dynamic configuration. That is to say we could configure the
+ server at different levels through this model. The top level is a global
+ level which is defined as the container 'network-ranges'. The following
+ levels are defined as sub-containers under it. The 'network-ranges'
+ contains the parameters (e.g. option-sets) that would be allocated to
+ all the clients served by this server.";
+
+ leaf option-set-id {
+ type leafref {
+ path "/server/server-config/option-sets/option-set/option-set-id";
+ }
+ description
+ "The ID field of relevant global option-set to be provisioned to
+ clients.";
+ }
+ list network-range {
+ key network-range-id;
+ description
+ "Under the 'network-ranges' container, a 'network-range' list
+ is defined to configure the server at a network level which is also
+ considered as the second level. Different network are identified by the
+ key 'network-range-id'. This is because a server may have different
+ configuration parameters (e.g. option sets) for different networks.";
+ leaf network-range-id {
+ type uint32;
+ mandatory true;
+ description "equivalent to subnet id";
+ }
+ leaf network-description {
+ type string;
+ mandatory true;
+ description "description of the subnet";
+ }
+ leaf network-prefix {
+ type inet:ipv6-prefix;
+ mandatory true;
+ description "subnet prefix";
+ }
+ leaf option-set-id {
+ type leafref {
+ path "/server/server-config/option-sets/option-set/option-set-id";
+ }
+ description "The ID field of relevant option-set to be provisioned to
+ clients of this network-range.";
+ }
+
+ container address-pools {
+ description
+ "A container that describes the DHCPv6 server's
+ address pools.";
+ list address-pool {
+ key pool-id;
+ description "A DHCPv6 server can be configured with
+ several address pools. This list defines such address pools
+ which are distinguished by the key called 'pool-id'.";
+ leaf pool-id {
+ type uint32;
+ mandatory true;
+ description "pool id";
+ }
+ leaf pool-prefix {
+ type inet:ipv6-prefix;
+ mandatory true;
+ description "pool prefix";
+ }
+ leaf start-address {
+ type inet:ipv6-address-no-zone;
+ mandatory true;
+ description "start address";
+ }
+ leaf end-address {
+ type inet:ipv6-address-no-zone;
+ mandatory true;
+ description "end address";
+ }
+ leaf valid-lifetime {
+ type yang:timeticks;
+ mandatory true;
+ description "valid liftime for IA";
+ }
+ leaf renew-time {
+ type yang:timeticks;
+ mandatory true;
+ description "renew time";
+ }
+ leaf rebind-time {
+ type yang:timeticks;
+ mandatory true;
+ description "rebind time";
+ }
+ leaf preferred-lifetime {
+ type yang:timeticks;
+ mandatory true;
+ description "preferred lifetime for IA";
+ }
+ leaf rapid-commit {
+ type boolean;
+ mandatory true;
+ description "A boolean value specifies whether the pool
+ supports client-server exchanges involving two messages.";
+ }
+ leaf client-class {
+ type string;
+ description
+ "If this leaf is specified, this pool will only serve
+ the clients belonging to this class.";
+ }
+ leaf max-address-count {
+ type threshold;
+ mandatory true;
+ description "maximum count of addresses that can
+ be allocated in this pool. This value may be
+ less than count of total addresses.";
+ }
+ leaf option-set-id {
+ type leafref {
+ path "/server/server-config/option-sets/option-set/option-set-id";
+ }
+ mandatory true;
+ description "The ID field of relevant option-set to be
+ provisioned to clients of this address-pool.";
+ }
+ }
+ }
+
+ container pd-pools {
+ description "If a server supports prefix delegation function, this
+ container will be used to define the delegating router's prefix
+ pools.";
+ list pd-pool {
+ key pool-id;
+ description "Similar to server's address pools, a delegating
+ router can also be configured with multiple prefix pools
+ specified by a list called 'prefix-pool'.";
+ leaf pool-id {
+ type uint32;
+ mandatory true;
+ description "pool id";
+ }
+ leaf prefix {
+ type inet:ipv6-prefix;
+ mandatory true;
+ description "ipv6 prefix";
+ }
+ leaf prefix-length {
+ type uint8;
+ mandatory true;
+ description "prefix length";
+ }
+ leaf valid-lifetime {
+ type yang:timeticks;
+ mandatory true;
+ description "valid lifetime for IA";
+ }
+ leaf renew-time {
+ type yang:timeticks;
+ mandatory true;
+ description "renew time";
+ }
+ leaf rebind-time {
+ type yang:timeticks;
+ mandatory true;
+ description "rebind time";
+ }
+ leaf preferred-lifetime {
+ type yang:timeticks;
+ mandatory true;
+ description "preferred lifetime for IA";
+ }
+ leaf rapid-commit {
+ type boolean;
+ mandatory true;
+ description "A boolean value specifies whether the server
+ support client-server exchanges involving two messages defined.";
+ }
+ leaf client-class {
+ type string;
+ description "client class";
+ }
+ leaf max-pd-space-utilization {
+ type threshold;
+ mandatory true;
+ description "Maximum utilization of pd space in this pool";
+ }
+ leaf option-set-id {
+ type leafref {
+ path "/server/server-config/option-sets/option-set/option-set-id";
+ }
+ mandatory true;
+ description "The ID field of relevant option-set to be
+ provisioned to clients of this prefix-pool.";
+ }
+ }
+ }
+
+ container host-reservations {
+ description
+ "This container allows the server to make reservations at host level.";
+ list host-reservation {
+ key cli-id;
+ description "This list allows the server to reserve addresses,
+ prefixes, hostname and options for different clients.";
+ leaf cli-id {
+ type uint32;
+ mandatory true;
+ description "client id";
+ }
+
+ choice client-identifier {
+ description "When making reservations, the server needs to choose a
+ identifier to identify the client. Currently 'DUID' and 'hardware
+ address' are supported.";
+ case duid {
+ description "DUID";
+ uses dhcpv6-types:duid;
+ }
+ case hw-address {
+ description "hardware address";
+ leaf hardware-address {
+ type yang:mac-address;
+ description "MAC address of client";
+ }
+ }
+ }
+
+ leaf-list reserv-addr {
+ type inet:ipv6-address;
+ description "reserved addr";
+ }
+
+ list prefix-reservation {
+ key reserv-prefix-id;
+ description "reserved prefix reservation";
+ leaf reserv-prefix-id {
+ type uint32;
+ mandatory true;
+ description "reserved prefix id";
+ }
+ leaf reserv-prefix {
+ type inet:ipv6-prefix;
+ mandatory true;
+ description "reserved prefix";
+ }
+ leaf reserv-prefix-len {
+ type uint8;
+ mandatory true;
+ description "reserved prefix length";
+ }
+ }
+
+ leaf hostname {
+ type string;
+ description "reserved hostname";
+ }
+
+ leaf option-set-id {
+ type leafref {
+ path "/server/server-config/option-sets/option-set/option-set-id";
+ }
+ description "The ID field of relevant option-set to be provisioned
+ in the host reservation.";
+ }
+ }
+ }
+
+ }
+ }
+
+ container relay-opaque-paras {
+ description "This container contains some opaque values in Relay Agent
+ options that need to be configured on the server side only for value
+ match. Such Relay Agent options include Interface-Id option,
+ Remote-Id option and Subscriber-Id option.";
+ list relays {
+ key relay-name;
+ description "relay agents";
+ leaf relay-name {
+ type string;
+ mandatory true;
+ description "relay agent name";
+ }
+ list interface-info {
+ key if-name;
+ description "interface info";
+ leaf if-name {
+ type string;
+ mandatory true;
+ description "interface name";
+ }
+ leaf interface-id {
+ type string;
+ mandatory true;
+ description "interface id";
+ }
+ }
+ list subscribers {
+ key subscriber;
+ description "subscribers";
+ leaf subscriber {
+ type uint32;
+ mandatory true;
+ description "subscriber";
+ }
+ leaf subscriber-id {
+ type string;
+ mandatory true;
+ description "subscriber id";
+ }
+ }
+ list remote-host {
+ key ent-num;
+ description "remote host";
+ leaf ent-num {
+ type uint32;
+ mandatory true;
+ description "enterprise number";
+ }
+ leaf remote-id {
+ type string;
+ mandatory true;
+ description "remote id";
+ }
+ }
+ }
+ }
+
+ container rsoo-enabled-options {
+ description "rsoo enabled options";
+ list rsoo-enabled-option {
+ key option-code;
+ description "rsoo enabled option";
+ leaf option-code {
+ type uint16;
+ mandatory true;
+ description "option code";
+ }
+ leaf description {
+ type string;
+ mandatory true;
+ description "description of the option";
+ }
+ }
+ }
+
+ }
+
+ /*
+ * State data
+ */
+ container server-state {
+ config "false";
+ description "states of server";
+ container network-ranges {
+ description "This model supports a hierarchy to achieve dynamic configuration.
+ That is to say we could configure the server at different levels through
+ this model. The top level is a global level which is defined as the container
+ 'network-ranges'. The following levels are defined as sub-containers under it.
+ The 'network-ranges' contains the parameters (e.g. option-sets) that would be
+ allocated to all the clients served by this server.";
+ list network-range {
+ key network-range-id;
+ description "The ID field of relevant option-set to be provisioned
+ to clients of this network-range.";
+ leaf network-range-id {
+ type uint32;
+ mandatory true;
+ description "equivalent to subnet id";
+ }
+ container address-pools {
+ description "A container that describes the DHCPv6 server's address pools";
+ list address-pool {
+ key pool-id;
+ description "A DHCPv6 server can be configured with
+ several address pools. This list defines such address pools
+ which are distinguished by the key called 'pool-id'.";
+ leaf pool-id {
+ type uint32;
+ mandatory true;
+ description "pool id";
+ }
+ leaf total-address-count {
+ type uint64;
+ mandatory true;
+ description "count of total addresses in the pool";
+ }
+ leaf allocated-address-conut {
+ type uint64;
+ mandatory true;
+ description "count of allocated addresses in the pool";
+ }
+ }
+ list binding-info {
+ key cli-id;
+ description "A list that records a binding information for each DHCPv6
+ client that has already been allocated IPv6 addresses.";
+ leaf cli-id {
+ type uint32;
+ mandatory true;
+ description "client id";
+ }
+ container duid {
+ description "Read the DUID";
+ uses dhcpv6-types:duid;
+ }
+ list cli-ia {
+ key iaid;
+ description "client IA";
+ leaf ia-type {
+ type string;
+ mandatory true;
+ description "IA type";
+ }
+ leaf iaid {
+ type uint32;
+ mandatory true;
+ description "IAID";
+ }
+ leaf-list cli-addr {
+ type inet:ipv6-address;
+ description "client addr";
+ }
+ leaf pool-id {
+ type uint32;
+ mandatory true;
+ description "pool id";
+ }
+ }
+ }
+ }
+ container pd-pools {
+ description "If a server supports prefix delegation function,
+ this container will be used to define the delegating
+ router's prefix pools.";
+ list prefix-pool {
+ key pool-id;
+ description "Similar to server's address pools, a delegating
+ router can also be configured with multiple prefix pools
+ specified by a list called 'prefix-pool'.";
+ leaf pool-id {
+ type uint32;
+ mandatory true;
+ description "pool id";
+ }
+ leaf pd-space-utilization {
+ type threshold;
+ mandatory true;
+ description "current PD space utilization";
+ }
+ }
+ list binding-info {
+ key cli-id;
+ description "A list records a binding information for each DHCPv6
+ client that has already been alloated IPv6 prefixes.";
+ leaf cli-id {
+ type uint32;
+ mandatory true;
+ description "client id";
+ }
+ container duid {
+ description "Reads the DUID";
+ uses dhcpv6-types:duid;
+ }
+ list cli-iapd {
+ key iaid;
+ description "client IAPD";
+ leaf iaid {
+ type uint32;
+ mandatory true;
+ description "IAID";
+ }
+ leaf-list cli-prefix {
+ type inet:ipv6-prefix;
+ description "client ipv6 prefix";
+ }
+ leaf-list cli-prefix-len {
+ type uint8;
+ description "client prefix length";
+ }
+ leaf pool-id {
+ type uint32;
+ mandatory true;
+ description "pool id";
+ }
+ }
+ }
+ }
+
+ container host-reservations {
+ description "This container provides host reservations in the host level.";
+ list binding-info {
+ key cli-id;
+ description
+ "A list records a binding information for each DHCPv6
+ client that has already been alloated IPv6 addresses or prefixes
+ by host reservations.";
+ leaf cli-id {
+ type uint32;
+ mandatory true;
+ description "client id";
+ }
+ container duid {
+ description "Reads the DUID";
+ uses dhcpv6-types:duid;
+ }
+ list cli-ia {
+ key iaid;
+ description "client IA";
+ leaf ia-type {
+ type string;
+ mandatory true;
+ description "IA type, IA_NA or IA_TA";
+ }
+ leaf iaid {
+ type uint32;
+ mandatory true;
+ description "IAID";
+ }
+ leaf-list cli-addr {
+ type inet:ipv6-address;
+ description "client addr";
+ }
+ }
+
+ list cli-iapd {
+ key iaid;
+ description "client IAPD";
+ leaf iaid {
+ type uint32;
+ mandatory true;
+ description "IAID";
+ }
+ leaf-list cli-prefix {
+ type inet:ipv6-prefix;
+ description "client ipv6 prefix";
+ }
+ leaf-list cli-prefix-len {
+ type uint8;
+ description "client prefix length";
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+ container packet-stats {
+ description "A container presents the packet statistics related to
+ the DHCPv6 server.";
+ leaf solicit-count {
+ type uint32;
+ mandatory true;
+ description "solicit counter";
+ }
+ leaf request-count {
+ type uint32;
+ mandatory true;
+ description "request counter";
+ }
+ leaf renew-count {
+ type uint32;
+ mandatory true;
+ description "renew counter";
+ }
+ leaf rebind-count {
+ type uint32;
+ mandatory true;
+ description "rebind counter";
+ }
+ leaf decline-count {
+ type uint32;
+ mandatory true;
+ description "decline count";
+ }
+ leaf release-count {
+ type uint32;
+ mandatory true;
+ description "release counter";
+ }
+ leaf info-req-count {
+ type uint32;
+ mandatory true;
+ description "information request counter";
+ }
+ leaf advertise-count {
+ type uint32;
+ mandatory true;
+ description "advertise counter";
+ }
+ leaf confirm-count {
+ type uint32;
+ mandatory true;
+ description "confirm counter";
+ }
+ leaf reply-count {
+ type uint32;
+ mandatory true;
+ description "reply counter";
+ }
+ leaf reconfigure-count {
+ type uint32;
+ mandatory true;
+ description "reconfigure counter";
+ }
+ leaf relay-forward-count {
+ type uint32;
+ mandatory true;
+ description "relay forward counter";
+ }
+ leaf relay-reply-count {
+ type uint32;
+ mandatory true;
+ description "relay reply counter";
+ }
+ }
+ }
+ }
+
+ /*
+ * Notifications
+ */
+
+ notification notifications {
+ description "dhcpv6 server notification module";
+ container dhcpv6-server-event {
+ description "dhcpv6 server event";
+ container address-pool-running-out {
+ description "raised when the address pool is going to
+ run out. A threshold for utilization ratio of the pool has
+ been defined in the server feature so that it will notify the
+ administrator when the utilization ratio reaches the
+ threshold, and such threshold is a settable parameter";
+ leaf total-address-count {
+ type uint64;
+ mandatory true;
+ description "count of total addresses in the pool";
+ }
+ leaf max-address-count {
+ type uint64;
+ mandatory true;
+ description "maximum count of addresses that can be allocated
+ in the pool. This value may be less than count of total
+ addresses";
+ }
+ leaf allocated-address-conut {
+ type uint64;
+ mandatory true;
+ description "count of allocated addresses in the pool";
+ }
+ container duid {
+ description "server duid";
+ uses dhcpv6-types:duid;
+ }
+ leaf serv-name {
+ type string;
+ description "server name";
+ }
+ leaf pool-name {
+ type string;
+ mandatory true;
+ description "pool name";
+ }
+ }
+ container pd-pool-running-out {
+ description "raised when the address/prefix pool is going to
+ run out. A threshold for utilization ratio of the pool has
+ been defined in the server feature so that it will notify the
+ administrator when the utilization ratio reaches the
+ threshold, and such threshold is a settable parameter";
+ leaf max-pd-space-utilization {
+ type threshold;
+ mandatory true;
+ description "maximum pd space utilization";
+ }
+ leaf pd-space-utilization {
+ type threshold;
+ mandatory true;
+ description "current pd space utilization";
+ }
+ container duid {
+ description "Sets the DUID";
+ uses dhcpv6-types:duid;
+ }
+ leaf serv-name {
+ type string;
+ description "server name";
+ }
+ leaf pool-name {
+ type string;
+ mandatory true;
+ description "pool name";
+ }
+ }
+ container invalid-client-detected {
+ description "raised when the server has found a client which
+ can be regarded as a potential attacker. Some description
+ could also be included.";
+ container duid {
+ description "Sets the DUID";
+ uses dhcpv6-types:duid;
+ }
+ leaf description {
+ type string;
+ description "description of the event";
+ }
+ }
+ }
+ }
+}
diff --git a/src/share/yang/modules/ietf-dhcpv6-types@2018-09-04.yang b/src/share/yang/modules/ietf-dhcpv6-types@2018-09-04.yang
new file mode 100644
index 0000000..52feb28
--- /dev/null
+++ b/src/share/yang/modules/ietf-dhcpv6-types@2018-09-04.yang
@@ -0,0 +1,211 @@
+module ietf-dhcpv6-types {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-dhcpv6-types";
+ prefix "dhcpv6-types";
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import ietf-yang-types {
+ prefix yang;
+ }
+
+ organization "DHC WG";
+ contact
+ "cuiyong@tsinghua.edu.cn
+ lh.sunlinh@gmail.com
+ ian.farrer@telekom.de
+ sladjana.zechlin@telekom.de
+ hezihao9512@gmail.com";
+
+ description "This model defines a YANG data model that can be
+ used to define some commonly used DHCPv6 types";
+
+ revision 2018-09-04 {
+ description "";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ revision 2018-01-30 {
+ description "Initial revision";
+ reference "I-D: draft-ietf-dhc-dhcpv6-yang";
+ }
+
+ /*
+ * Grouping
+ */
+ grouping vendor-infor {
+ description "Vendor information.";
+ container vendor-info {
+ description "";
+ leaf ent-num {
+ type uint32;
+ mandatory true;
+ description "enterprise number";
+ }
+ leaf-list data {
+ type string;
+ description "specific vendor info";
+ }
+ }
+ }
+
+ grouping duid {
+ description
+ "Each server and client has only one DUID (DHCP Unique Identifier).
+ The DUID here identifies a unique DHCPv6 server for clients. DUID
+ consists of a two-octet type field and an arbitrary length (no more
+ than 128 bytes) content field. Currently there are four defined types
+ of DUIDs in RFC3315 and RFC6355 - DUID-LLT, DUID-EN, DUID-LL and
+ DUID-UUID. DUID-Unknown represents those unconventional DUIDs.";
+ reference "RFC3315: Section 9 and RFC6355: Section 4";
+ leaf type-code {
+ type uint16;
+ default 65535;
+ description "Type code of this DUID";
+ }
+ choice duid-type {
+ default duid-unknown;
+ description "Selects the format for the DUID.";
+ case duid-llt {
+ description "DUID Based on Link-layer Address Plus Time
+ (Type 1 - DUID-LLT)";
+ reference "RFC3315 Section 9.2";
+ leaf duid-llt-hardware-type {
+ type uint16;
+ description "Hardware type as assigned by IANA (RFC826).";
+ }
+ leaf duid-llt-time {
+ type yang:timeticks;
+ description "The time value is the time that the DUID is
+ generated represented in seconds since midnight (UTC),
+ January 1, 2000, modulo 2^32.";
+ }
+ leaf duid-llt-link-layer-addr {
+ type yang:mac-address;
+ description "Link-layer address as described in RFC2464";
+ }
+ }
+ case duid-en {
+ description "DUID Assigned by Vendor Based on Enterprise Number
+ (Type 2 - DUID-EN)";
+ reference "RFC3315 Section 9.3";
+ leaf duid-en-enterprise-number {
+ type uint32;
+ description "Vendor's registered Private Enterprise Number as
+ maintained by IANA";
+ }
+ leaf duid-en-identifier {
+ type string;
+ description "Indentifier, unique to the device that is
+ using it";
+ }
+ }
+ case duid-ll {
+ description "DUID Based on Link-layer Address (Type 3 - DUID-LL)";
+ reference "RFC3315 Section 9.4";
+ leaf duid-ll-hardware-type {
+ type uint16;
+ description "Hardware type as assigned by IANA (RFC826).";
+ }
+ leaf duid-ll-link-layer-addr {
+ type yang:mac-address;
+ description "Link-layer address as described in RFC2464";
+ }
+ }
+ case duid-uuid {
+ description "DUID Based on Universally Unique Identifier
+ (Type 4 - DUID-UUID)";
+ reference "RFC6335 Defination of the UUID-Based Unique Identifier";
+ leaf uuid {
+ type yang:uuid;
+ description "A Universally Unique IDentifier in the string
+ representation defined in RFC 4122. The canonical
+ representation uses lowercase characters";
+ }
+ }
+ case duid-unknown {
+ description "DUID based on free raw bytes";
+ leaf data {
+ type binary;
+ description "The bits to be used as the identifier";
+ }
+ }
+ }
+ }
+
+ grouping portset-para {
+ description "portset parameters";
+ container port-parameter {
+ description "port parameter";
+ leaf offset {
+ type uint8;
+ mandatory true;
+ description "offset in a port set";
+ }
+ leaf psid-len {
+ type uint8;
+ mandatory true;
+ description "length of a psid";
+ }
+ leaf psid {
+ type uint16;
+ mandatory true;
+ description "psid value";
+ }
+ }
+ }
+
+ grouping iaid {
+ description "IA is a construct through which a server and a
+ client can identify, group, and manage a set of related IPv6
+ addresses. The key of the list is a 4-byte number IAID defined
+ in [RFC3315].";
+ list identity-association {
+ config "false";
+ description "IA";
+ leaf iaid {
+ type uint32;
+ mandatory true;
+ description "IAID";
+ }
+ leaf ia-type {
+ type string;
+ mandatory true;
+ description "IA type";
+ }
+ leaf-list ipv6-addr {
+ type inet:ipv6-address;
+ description "ipv6 address";
+ }
+ leaf-list ipv6-prefix {
+ type inet:ipv6-prefix;
+ description "ipv6 prefix";
+ }
+ leaf-list prefix-length {
+ type uint8;
+ description "ipv6 prefix length";
+ }
+ leaf t1-time {
+ type yang:timeticks;
+ mandatory true;
+ description "t1 time";
+ }
+ leaf t2-time {
+ type yang:timeticks;
+ mandatory true;
+ description "t2 time";
+ }
+ leaf preferred-lifetime {
+ type yang:timeticks;
+ mandatory true;
+ description "preferred lifetime";
+ }
+ leaf valid-lifetime {
+ type yang:timeticks;
+ mandatory true;
+ description "valid lifetime";
+ }
+ }
+ }
+}
diff --git a/src/share/yang/modules/ietf-inet-types@2013-07-15.yang b/src/share/yang/modules/ietf-inet-types@2013-07-15.yang
new file mode 100644
index 0000000..2f14270
--- /dev/null
+++ b/src/share/yang/modules/ietf-inet-types@2013-07-15.yang
@@ -0,0 +1,457 @@
+module ietf-inet-types {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-inet-types";
+ prefix "inet";
+
+ organization
+ "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netmod/>
+ WG List: <mailto:netmod@ietf.org>
+
+ WG Chair: David Kessens
+ <mailto:david.kessens@nsn.com>
+
+ WG Chair: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>
+
+ Editor: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>";
+
+ description
+ "This module contains a collection of generally useful derived
+ YANG data types for Internet addresses and related things.
+
+ Copyright (c) 2013 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 6991; see
+ the RFC itself for full legal notices.";
+
+ revision 2013-07-15 {
+ description
+ "This revision adds the following new data types:
+ - ip-address-no-zone
+ - ipv4-address-no-zone
+ - ipv6-address-no-zone";
+ reference
+ "RFC 6991: Common YANG Data Types";
+ }
+
+ revision 2010-09-24 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 6021: Common YANG Data Types";
+ }
+
+ /*** collection of types related to protocol fields ***/
+
+ typedef ip-version {
+ type enumeration {
+ enum unknown {
+ value "0";
+ description
+ "An unknown or unspecified version of the Internet
+ protocol.";
+ }
+ enum ipv4 {
+ value "1";
+ description
+ "The IPv4 protocol as defined in RFC 791.";
+ }
+ enum ipv6 {
+ value "2";
+ description
+ "The IPv6 protocol as defined in RFC 2460.";
+ }
+ }
+ description
+ "This value represents the version of the IP protocol.
+
+ In the value set and its semantics, this type is equivalent
+ to the InetVersion textual convention of the SMIv2.";
+ reference
+ "RFC 791: Internet Protocol
+ RFC 2460: Internet Protocol, Version 6 (IPv6) Specification
+ RFC 4001: Textual Conventions for Internet Network Addresses";
+ }
+
+ typedef dscp {
+ type uint8 {
+ range "0..63";
+ }
+ description
+ "The dscp type represents a Differentiated Services Code Point
+ that may be used for marking packets in a traffic stream.
+ In the value set and its semantics, this type is equivalent
+ to the Dscp textual convention of the SMIv2.";
+ reference
+ "RFC 3289: Management Information Base for the Differentiated
+ Services Architecture
+ RFC 2474: Definition of the Differentiated Services Field
+ (DS Field) in the IPv4 and IPv6 Headers
+ RFC 2780: IANA Allocation Guidelines For Values In
+ the Internet Protocol and Related Headers";
+ }
+
+ typedef ipv6-flow-label {
+ type uint32 {
+ range "0..1048575";
+ }
+ description
+ "The ipv6-flow-label type represents the flow identifier or Flow
+ Label in an IPv6 packet header that may be used to
+ discriminate traffic flows.
+
+ In the value set and its semantics, this type is equivalent
+ to the IPv6FlowLabel textual convention of the SMIv2.";
+ reference
+ "RFC 3595: Textual Conventions for IPv6 Flow Label
+ RFC 2460: Internet Protocol, Version 6 (IPv6) Specification";
+ }
+
+ typedef port-number {
+ type uint16 {
+ range "0..65535";
+ }
+ description
+ "The port-number type represents a 16-bit port number of an
+ Internet transport-layer protocol such as UDP, TCP, DCCP, or
+ SCTP. Port numbers are assigned by IANA. A current list of
+ all assignments is available from <http://www.iana.org/>.
+
+ Note that the port number value zero is reserved by IANA. In
+ situations where the value zero does not make sense, it can
+ be excluded by subtyping the port-number type.
+ In the value set and its semantics, this type is equivalent
+ to the InetPortNumber textual convention of the SMIv2.";
+ reference
+ "RFC 768: User Datagram Protocol
+ RFC 793: Transmission Control Protocol
+ RFC 4960: Stream Control Transmission Protocol
+ RFC 4340: Datagram Congestion Control Protocol (DCCP)
+ RFC 4001: Textual Conventions for Internet Network Addresses";
+ }
+
+ /*** collection of types related to autonomous systems ***/
+
+ typedef as-number {
+ type uint32;
+ description
+ "The as-number type represents autonomous system numbers
+ which identify an Autonomous System (AS). An AS is a set
+ of routers under a single technical administration, using
+ an interior gateway protocol and common metrics to route
+ packets within the AS, and using an exterior gateway
+ protocol to route packets to other ASes. IANA maintains
+ the AS number space and has delegated large parts to the
+ regional registries.
+
+ Autonomous system numbers were originally limited to 16
+ bits. BGP extensions have enlarged the autonomous system
+ number space to 32 bits. This type therefore uses an uint32
+ base type without a range restriction in order to support
+ a larger autonomous system number space.
+
+ In the value set and its semantics, this type is equivalent
+ to the InetAutonomousSystemNumber textual convention of
+ the SMIv2.";
+ reference
+ "RFC 1930: Guidelines for creation, selection, and registration
+ of an Autonomous System (AS)
+ RFC 4271: A Border Gateway Protocol 4 (BGP-4)
+ RFC 4001: Textual Conventions for Internet Network Addresses
+ RFC 6793: BGP Support for Four-Octet Autonomous System (AS)
+ Number Space";
+ }
+
+ /*** collection of types related to IP addresses and hostnames ***/
+
+ typedef ip-address {
+ type union {
+ type inet:ipv4-address;
+ type inet:ipv6-address;
+ }
+ description
+ "The ip-address type represents an IP address and is IP
+ version neutral. The format of the textual representation
+ implies the IP version. This type supports scoped addresses
+ by allowing zone identifiers in the address format.";
+ reference
+ "RFC 4007: IPv6 Scoped Address Architecture";
+ }
+
+ typedef ipv4-address {
+ type string {
+ pattern
+ '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}'
+ + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
+ + '(%[\p{N}\p{L}]+)?';
+ }
+ description
+ "The ipv4-address type represents an IPv4 address in
+ dotted-quad notation. The IPv4 address may include a zone
+ index, separated by a % sign.
+
+ The zone index is used to disambiguate identical address
+ values. For link-local addresses, the zone index will
+ typically be the interface index number or the name of an
+ interface. If the zone index is not present, the default
+ zone of the device will be used.
+
+ The canonical format for the zone index is the numerical
+ format";
+ }
+
+ typedef ipv6-address {
+ type string {
+ pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}'
+ + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|'
+ + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}'
+ + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))'
+ + '(%[\p{N}\p{L}]+)?';
+ pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|'
+ + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)'
+ + '(%.+)?';
+ }
+ description
+ "The ipv6-address type represents an IPv6 address in full,
+ mixed, shortened, and shortened-mixed notation. The IPv6
+ address may include a zone index, separated by a % sign.
+
+ The zone index is used to disambiguate identical address
+ values. For link-local addresses, the zone index will
+ typically be the interface index number or the name of an
+ interface. If the zone index is not present, the default
+ zone of the device will be used.
+
+ The canonical format of IPv6 addresses uses the textual
+ representation defined in Section 4 of RFC 5952. The
+ canonical format for the zone index is the numerical
+ format as described in Section 11.2 of RFC 4007.";
+ reference
+ "RFC 4291: IP Version 6 Addressing Architecture
+ RFC 4007: IPv6 Scoped Address Architecture
+ RFC 5952: A Recommendation for IPv6 Address Text
+ Representation";
+ }
+
+ typedef ip-address-no-zone {
+ type union {
+ type inet:ipv4-address-no-zone;
+ type inet:ipv6-address-no-zone;
+ }
+ description
+ "The ip-address-no-zone type represents an IP address and is
+ IP version neutral. The format of the textual representation
+ implies the IP version. This type does not support scoped
+ addresses since it does not allow zone identifiers in the
+ address format.";
+ reference
+ "RFC 4007: IPv6 Scoped Address Architecture";
+ }
+
+ typedef ipv4-address-no-zone {
+ type inet:ipv4-address {
+ pattern '[0-9\.]*';
+ }
+ description
+ "An IPv4 address without a zone index. This type, derived from
+ ipv4-address, may be used in situations where the zone is
+ known from the context and hence no zone index is needed.";
+ }
+
+ typedef ipv6-address-no-zone {
+ type inet:ipv6-address {
+ pattern '[0-9a-fA-F:\.]*';
+ }
+ description
+ "An IPv6 address without a zone index. This type, derived from
+ ipv6-address, may be used in situations where the zone is
+ known from the context and hence no zone index is needed.";
+ reference
+ "RFC 4291: IP Version 6 Addressing Architecture
+ RFC 4007: IPv6 Scoped Address Architecture
+ RFC 5952: A Recommendation for IPv6 Address Text
+ Representation";
+ }
+
+ typedef ip-prefix {
+ type union {
+ type inet:ipv4-prefix;
+ type inet:ipv6-prefix;
+ }
+ description
+ "The ip-prefix type represents an IP prefix and is IP
+ version neutral. The format of the textual representations
+ implies the IP version.";
+ }
+
+ typedef ipv4-prefix {
+ type string {
+ pattern
+ '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}'
+ + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
+ + '/(([0-9])|([1-2][0-9])|(3[0-2]))';
+ }
+ description
+ "The ipv4-prefix type represents an IPv4 address prefix.
+ The prefix length is given by the number following the
+ slash character and must be less than or equal to 32.
+
+ A prefix length value of n corresponds to an IP address
+ mask that has n contiguous 1-bits from the most
+ significant bit (MSB) and all other bits set to 0.
+
+ The canonical format of an IPv4 prefix has all bits of
+ the IPv4 address set to zero that are not part of the
+ IPv4 prefix.";
+ }
+
+ typedef ipv6-prefix {
+ type string {
+ pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}'
+ + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|'
+ + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}'
+ + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))'
+ + '(/(([0-9])|([0-9]{2})|(1[0-1][0-9])|(12[0-8])))';
+ pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|'
+ + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)'
+ + '(/.+)';
+ }
+ description
+ "The ipv6-prefix type represents an IPv6 address prefix.
+ The prefix length is given by the number following the
+ slash character and must be less than or equal to 128.
+
+ A prefix length value of n corresponds to an IP address
+ mask that has n contiguous 1-bits from the most
+ significant bit (MSB) and all other bits set to 0.
+
+ The IPv6 address should have all bits that do not belong
+ to the prefix set to zero.
+
+ The canonical format of an IPv6 prefix has all bits of
+ the IPv6 address set to zero that are not part of the
+ IPv6 prefix. Furthermore, the IPv6 address is represented
+ as defined in Section 4 of RFC 5952.";
+ reference
+ "RFC 5952: A Recommendation for IPv6 Address Text
+ Representation";
+ }
+
+ /*** collection of domain name and URI types ***/
+
+ typedef domain-name {
+ type string {
+ length "1..253";
+ pattern
+ '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*'
+ + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)'
+ + '|\.';
+ }
+ description
+ "The domain-name type represents a DNS domain name. The
+ name SHOULD be fully qualified whenever possible.
+
+ Internet domain names are only loosely specified. Section
+ 3.5 of RFC 1034 recommends a syntax (modified in Section
+ 2.1 of RFC 1123). The pattern above is intended to allow
+ for current practice in domain name use, and some possible
+ future expansion. It is designed to hold various types of
+ domain names, including names used for A or AAAA records
+ (host names) and other records, such as SRV records. Note
+ that Internet host names have a stricter syntax (described
+ in RFC 952) than the DNS recommendations in RFCs 1034 and
+ 1123, and that systems that want to store host names in
+ schema nodes using the domain-name type are recommended to
+ adhere to this stricter standard to ensure interoperability.
+
+ The encoding of DNS names in the DNS protocol is limited
+ to 255 characters. Since the encoding consists of labels
+ prefixed by a length bytes and there is a trailing NULL
+ byte, only 253 characters can appear in the textual dotted
+ notation.
+
+ The description clause of schema nodes using the domain-name
+ type MUST describe when and how these names are resolved to
+ IP addresses. Note that the resolution of a domain-name value
+ may require to query multiple DNS records (e.g., A for IPv4
+ and AAAA for IPv6). The order of the resolution process and
+ which DNS record takes precedence can either be defined
+ explicitly or may depend on the configuration of the
+ resolver.
+
+ Domain-name values use the US-ASCII encoding. Their canonical
+ format uses lowercase US-ASCII characters. Internationalized
+ domain names MUST be A-labels as per RFC 5890.";
+ reference
+ "RFC 952: DoD Internet Host Table Specification
+ RFC 1034: Domain Names - Concepts and Facilities
+ RFC 1123: Requirements for Internet Hosts -- Application
+ and Support
+ RFC 2782: A DNS RR for specifying the location of services
+ (DNS SRV)
+ RFC 5890: Internationalized Domain Names in Applications
+ (IDNA): Definitions and Document Framework";
+ }
+
+ typedef host {
+ type union {
+ type inet:ip-address;
+ type inet:domain-name;
+ }
+ description
+ "The host type represents either an IP address or a DNS
+ domain name.";
+ }
+
+ typedef uri {
+ type string;
+ description
+ "The uri type represents a Uniform Resource Identifier
+ (URI) as defined by STD 66.
+
+ Objects using the uri type MUST be in US-ASCII encoding,
+ and MUST be normalized as described by RFC 3986 Sections
+ 6.2.1, 6.2.2.1, and 6.2.2.2. All unnecessary
+ percent-encoding is removed, and all case-insensitive
+ characters are set to lowercase except for hexadecimal
+ digits, which are normalized to uppercase as described in
+ Section 6.2.2.1.
+
+ The purpose of this normalization is to help provide
+ unique URIs. Note that this normalization is not
+ sufficient to provide uniqueness. Two URIs that are
+ textually distinct after this normalization may still be
+ equivalent.
+
+ Objects using the uri type may restrict the schemes that
+ they permit. For example, 'data:' and 'urn:' schemes
+ might not be appropriate.
+
+ A zero-length URI is not a valid URI. This can be used to
+ express 'URI absent' where required.
+
+ In the value set and its semantics, this type is equivalent
+ to the Uri SMIv2 textual convention defined in RFC 5017.";
+ reference
+ "RFC 3986: Uniform Resource Identifier (URI): Generic Syntax
+ RFC 3305: Report from the Joint W3C/IETF URI Planning Interest
+ Group: Uniform Resource Identifiers (URIs), URLs,
+ and Uniform Resource Names (URNs): Clarifications
+ and Recommendations
+ RFC 5017: MIB Textual Conventions for Uniform Resource
+ Identifiers (URIs)";
+ }
+
+}
diff --git a/src/share/yang/modules/ietf-interfaces@2018-02-20.yang b/src/share/yang/modules/ietf-interfaces@2018-02-20.yang
new file mode 100644
index 0000000..f66c205
--- /dev/null
+++ b/src/share/yang/modules/ietf-interfaces@2018-02-20.yang
@@ -0,0 +1,1123 @@
+module ietf-interfaces {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces";
+ prefix if;
+
+ import ietf-yang-types {
+ prefix yang;
+ }
+
+ organization
+ "IETF NETMOD (Network Modeling) Working Group";
+
+ contact
+ "WG Web: <https://datatracker.ietf.org/wg/netmod/>
+ WG List: <mailto:netmod@ietf.org>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>";
+
+ description
+ "This module contains a collection of YANG definitions for
+ managing network interfaces.
+
+ Copyright (c) 2018 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (https://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 8343; see
+ the RFC itself for full legal notices.";
+
+ revision 2018-02-20 {
+ description
+ "Updated to support NMDA.";
+ reference
+ "RFC 8343: A YANG Data Model for Interface Management";
+ }
+
+ revision 2014-05-08 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 7223: A YANG Data Model for Interface Management";
+ }
+
+ /*
+ * Typedefs
+ */
+
+ typedef interface-ref {
+ type leafref {
+ path "/if:interfaces/if:interface/if:name";
+ }
+ description
+ "This type is used by data models that need to reference
+ interfaces.";
+ }
+
+ /*
+ * Identities
+ */
+
+ identity interface-type {
+ description
+ "Base identity from which specific interface types are
+ derived.";
+ }
+
+ /*
+ * Features
+ */
+
+ feature arbitrary-names {
+ description
+ "This feature indicates that the device allows user-controlled
+ interfaces to be named arbitrarily.";
+ }
+ feature pre-provisioning {
+ description
+ "This feature indicates that the device supports
+ pre-provisioning of interface configuration, i.e., it is
+ possible to configure an interface whose physical interface
+ hardware is not present on the device.";
+ }
+ feature if-mib {
+ description
+ "This feature indicates that the device implements
+ the IF-MIB.";
+ reference
+ "RFC 2863: The Interfaces Group MIB";
+ }
+
+ /*
+ * Data nodes
+ */
+
+ container interfaces {
+ description
+ "Interface parameters.";
+
+ list interface {
+ key "name";
+
+ description
+ "The list of interfaces on the device.
+
+ The status of an interface is available in this list in the
+ operational state. If the configuration of a
+ system-controlled interface cannot be used by the system
+ (e.g., the interface hardware present does not match the
+ interface type), then the configuration is not applied to
+ the system-controlled interface shown in the operational
+ state. If the configuration of a user-controlled interface
+ cannot be used by the system, the configured interface is
+ not instantiated in the operational state.
+
+ System-controlled interfaces created by the system are
+ always present in this list in the operational state,
+ whether or not they are configured.";
+
+ leaf name {
+ type string;
+ description
+ "The name of the interface.
+
+ A device MAY restrict the allowed values for this leaf,
+ possibly depending on the type of the interface.
+ For system-controlled interfaces, this leaf is the
+ device-specific name of the interface.
+
+ If a client tries to create configuration for a
+ system-controlled interface that is not present in the
+ operational state, the server MAY reject the request if
+ the implementation does not support pre-provisioning of
+ interfaces or if the name refers to an interface that can
+ never exist in the system. A Network Configuration
+ Protocol (NETCONF) server MUST reply with an rpc-error
+ with the error-tag 'invalid-value' in this case.
+
+ If the device supports pre-provisioning of interface
+ configuration, the 'pre-provisioning' feature is
+ advertised.
+
+ If the device allows arbitrarily named user-controlled
+ interfaces, the 'arbitrary-names' feature is advertised.
+
+ When a configured user-controlled interface is created by
+ the system, it is instantiated with the same name in the
+ operational state.
+
+ A server implementation MAY map this leaf to the ifName
+ MIB object. Such an implementation needs to use some
+ mechanism to handle the differences in size and characters
+ allowed between this leaf and ifName. The definition of
+ such a mechanism is outside the scope of this document.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifName";
+ }
+
+ leaf description {
+ type string;
+ description
+ "A textual description of the interface.
+
+ A server implementation MAY map this leaf to the ifAlias
+ MIB object. Such an implementation needs to use some
+ mechanism to handle the differences in size and characters
+ allowed between this leaf and ifAlias. The definition of
+ such a mechanism is outside the scope of this document.
+
+ Since ifAlias is defined to be stored in non-volatile
+ storage, the MIB implementation MUST map ifAlias to the
+ value of 'description' in the persistently stored
+ configuration.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAlias";
+ }
+
+ leaf type {
+ type identityref {
+ base interface-type;
+ }
+ mandatory true;
+ description
+ "The type of the interface.
+
+ When an interface entry is created, a server MAY
+ initialize the type leaf with a valid value, e.g., if it
+ is possible to derive the type from the name of the
+ interface.
+
+ If a client tries to set the type of an interface to a
+ value that can never be used by the system, e.g., if the
+ type is not supported or if the type does not match the
+ name of the interface, the server MUST reject the request.
+ A NETCONF server MUST reply with an rpc-error with the
+ error-tag 'invalid-value' in this case.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifType";
+ }
+
+ leaf enabled {
+ type boolean;
+ default "true";
+ description
+ "This leaf contains the configured, desired state of the
+ interface.
+
+ Systems that implement the IF-MIB use the value of this
+ leaf in the intended configuration to set
+ IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry
+ has been initialized, as described in RFC 2863.
+
+ Changes in this leaf in the intended configuration are
+ reflected in ifAdminStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAdminStatus";
+ }
+
+ leaf link-up-down-trap-enable {
+ if-feature if-mib;
+ type enumeration {
+ enum enabled {
+ value 1;
+ description
+ "The device will generate linkUp/linkDown SNMP
+ notifications for this interface.";
+ }
+ enum disabled {
+ value 2;
+ description
+ "The device will not generate linkUp/linkDown SNMP
+ notifications for this interface.";
+ }
+ }
+ description
+ "Controls whether linkUp/linkDown SNMP notifications
+ should be generated for this interface.
+
+ If this node is not configured, the value 'enabled' is
+ operationally used by the server for interfaces that do
+ not operate on top of any other interface (i.e., there are
+ no 'lower-layer-if' entries), and 'disabled' otherwise.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifLinkUpDownTrapEnable";
+ }
+
+ leaf admin-status {
+ if-feature if-mib;
+ type enumeration {
+ enum up {
+ value 1;
+ description
+ "Ready to pass packets.";
+ }
+ enum down {
+ value 2;
+ description
+ "Not ready to pass packets and not in some test mode.";
+ }
+ enum testing {
+ value 3;
+ description
+ "In some test mode.";
+ }
+ }
+ config false;
+ mandatory true;
+ description
+ "The desired state of the interface.
+
+ This leaf has the same read semantics as ifAdminStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAdminStatus";
+ }
+
+ leaf oper-status {
+ type enumeration {
+ enum up {
+ value 1;
+ description
+ "Ready to pass packets.";
+ }
+ enum down {
+ value 2;
+
+ description
+ "The interface does not pass any packets.";
+ }
+ enum testing {
+ value 3;
+ description
+ "In some test mode. No operational packets can
+ be passed.";
+ }
+ enum unknown {
+ value 4;
+ description
+ "Status cannot be determined for some reason.";
+ }
+ enum dormant {
+ value 5;
+ description
+ "Waiting for some external event.";
+ }
+ enum not-present {
+ value 6;
+ description
+ "Some component (typically hardware) is missing.";
+ }
+ enum lower-layer-down {
+ value 7;
+ description
+ "Down due to state of lower-layer interface(s).";
+ }
+ }
+ config false;
+ mandatory true;
+ description
+ "The current operational state of the interface.
+
+ This leaf has the same semantics as ifOperStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOperStatus";
+ }
+
+ leaf last-change {
+ type yang:date-and-time;
+ config false;
+ description
+ "The time the interface entered its current operational
+ state. If the current state was entered prior to the
+ last re-initialization of the local network management
+ subsystem, then this node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifLastChange";
+ }
+
+ leaf if-index {
+ if-feature if-mib;
+ type int32 {
+ range "1..2147483647";
+ }
+ config false;
+ mandatory true;
+ description
+ "The ifIndex value for the ifEntry represented by this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifIndex";
+ }
+
+ leaf phys-address {
+ type yang:phys-address;
+ config false;
+ description
+ "The interface's address at its protocol sub-layer. For
+ example, for an 802.x interface, this object normally
+ contains a Media Access Control (MAC) address. The
+ interface's media-specific modules must define the bit
+ and byte ordering and the format of the value of this
+ object. For interfaces that do not have such an address
+ (e.g., a serial line), this node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifPhysAddress";
+ }
+
+ leaf-list higher-layer-if {
+ type interface-ref;
+ config false;
+ description
+ "A list of references to interfaces layered on top of this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifStackTable";
+ }
+
+ leaf-list lower-layer-if {
+ type interface-ref;
+ config false;
+
+ description
+ "A list of references to interfaces layered underneath this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifStackTable";
+ }
+
+ leaf speed {
+ type yang:gauge64;
+ units "bits/second";
+ config false;
+ description
+ "An estimate of the interface's current bandwidth in bits
+ per second. For interfaces that do not vary in
+ bandwidth or for those where no accurate estimation can
+ be made, this node should contain the nominal bandwidth.
+ For interfaces that have no concept of bandwidth, this
+ node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifSpeed, ifHighSpeed";
+ }
+
+ container statistics {
+ config false;
+ description
+ "A collection of interface-related statistics objects.";
+
+ leaf discontinuity-time {
+ type yang:date-and-time;
+ mandatory true;
+ description
+ "The time on the most recent occasion at which any one or
+ more of this interface's counters suffered a
+ discontinuity. If no such discontinuities have occurred
+ since the last re-initialization of the local management
+ subsystem, then this node contains the time the local
+ management subsystem re-initialized itself.";
+ }
+
+ leaf in-octets {
+ type yang:counter64;
+ description
+ "The total number of octets received on the interface,
+ including framing characters.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCInOctets";
+ }
+
+ leaf in-unicast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were not addressed to a
+ multicast or broadcast address at this sub-layer.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts";
+ }
+
+ leaf in-broadcast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were addressed to a broadcast
+ address at this sub-layer.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCInBroadcastPkts";
+ }
+
+ leaf in-multicast-pkts {
+ type yang:counter64;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were addressed to a multicast
+ address at this sub-layer. For a MAC-layer protocol,
+ this includes both Group and Functional addresses.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCInMulticastPkts";
+ }
+
+ leaf in-discards {
+ type yang:counter32;
+ description
+ "The number of inbound packets that were chosen to be
+ discarded even though no errors had been detected to
+ prevent their being deliverable to a higher-layer
+ protocol. One possible reason for discarding such a
+ packet could be to free up buffer space.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInDiscards";
+ }
+
+ leaf in-errors {
+ type yang:counter32;
+ description
+ "For packet-oriented interfaces, the number of inbound
+ packets that contained errors preventing them from being
+ deliverable to a higher-layer protocol. For character-
+ oriented or fixed-length interfaces, the number of
+ inbound transmission units that contained errors
+ preventing them from being deliverable to a higher-layer
+ protocol.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInErrors";
+ }
+
+ leaf in-unknown-protos {
+ type yang:counter32;
+
+ description
+ "For packet-oriented interfaces, the number of packets
+ received via the interface that were discarded because
+ of an unknown or unsupported protocol. For
+ character-oriented or fixed-length interfaces that
+ support protocol multiplexing, the number of
+ transmission units received via the interface that were
+ discarded because of an unknown or unsupported protocol.
+ For any interface that does not support protocol
+ multiplexing, this counter is not present.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos";
+ }
+
+ leaf out-octets {
+ type yang:counter64;
+ description
+ "The total number of octets transmitted out of the
+ interface, including framing characters.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCOutOctets";
+ }
+
+ leaf out-unicast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted and that were not addressed
+ to a multicast or broadcast address at this sub-layer,
+ including those that were discarded or not sent.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts";
+ }
+
+ leaf out-broadcast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted and that were addressed to a
+ broadcast address at this sub-layer, including those
+ that were discarded or not sent.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCOutBroadcastPkts";
+ }
+
+ leaf out-multicast-pkts {
+ type yang:counter64;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted and that were addressed to a
+ multicast address at this sub-layer, including those
+ that were discarded or not sent. For a MAC-layer
+ protocol, this includes both Group and Functional
+ addresses.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCOutMulticastPkts";
+ }
+
+ leaf out-discards {
+ type yang:counter32;
+ description
+ "The number of outbound packets that were chosen to be
+ discarded even though no errors had been detected to
+ prevent their being transmitted. One possible reason
+ for discarding such a packet could be to free up buffer
+ space.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOutDiscards";
+ }
+
+ leaf out-errors {
+ type yang:counter32;
+ description
+ "For packet-oriented interfaces, the number of outbound
+ packets that could not be transmitted because of errors.
+ For character-oriented or fixed-length interfaces, the
+ number of outbound transmission units that could not be
+ transmitted because of errors.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOutErrors";
+ }
+ }
+
+ }
+ }
+
+ /*
+ * Legacy typedefs
+ */
+
+ typedef interface-state-ref {
+ type leafref {
+ path "/if:interfaces-state/if:interface/if:name";
+ }
+ status deprecated;
+ description
+ "This type is used by data models that need to reference
+ the operationally present interfaces.";
+ }
+
+ /*
+ * Legacy operational state data nodes
+ */
+
+ container interfaces-state {
+ config false;
+ status deprecated;
+ description
+ "Data nodes for the operational state of interfaces.";
+
+ list interface {
+ key "name";
+ status deprecated;
+
+ description
+ "The list of interfaces on the device.
+
+ System-controlled interfaces created by the system are
+ always present in this list, whether or not they are
+ configured.";
+
+ leaf name {
+ type string;
+ status deprecated;
+ description
+ "The name of the interface.
+
+ A server implementation MAY map this leaf to the ifName
+ MIB object. Such an implementation needs to use some
+ mechanism to handle the differences in size and characters
+ allowed between this leaf and ifName. The definition of
+ such a mechanism is outside the scope of this document.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifName";
+ }
+
+ leaf type {
+ type identityref {
+ base interface-type;
+ }
+ mandatory true;
+ status deprecated;
+ description
+ "The type of the interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifType";
+ }
+
+ leaf admin-status {
+ if-feature if-mib;
+ type enumeration {
+ enum up {
+ value 1;
+ description
+ "Ready to pass packets.";
+ }
+ enum down {
+ value 2;
+ description
+ "Not ready to pass packets and not in some test mode.";
+ }
+ enum testing {
+ value 3;
+ description
+ "In some test mode.";
+ }
+ }
+ mandatory true;
+ status deprecated;
+ description
+ "The desired state of the interface.
+
+ This leaf has the same read semantics as ifAdminStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifAdminStatus";
+ }
+
+ leaf oper-status {
+ type enumeration {
+ enum up {
+ value 1;
+ description
+ "Ready to pass packets.";
+ }
+ enum down {
+ value 2;
+ description
+ "The interface does not pass any packets.";
+ }
+ enum testing {
+ value 3;
+ description
+ "In some test mode. No operational packets can
+ be passed.";
+ }
+ enum unknown {
+ value 4;
+ description
+ "Status cannot be determined for some reason.";
+ }
+ enum dormant {
+ value 5;
+ description
+ "Waiting for some external event.";
+ }
+ enum not-present {
+ value 6;
+ description
+ "Some component (typically hardware) is missing.";
+ }
+ enum lower-layer-down {
+ value 7;
+ description
+ "Down due to state of lower-layer interface(s).";
+ }
+ }
+ mandatory true;
+ status deprecated;
+ description
+ "The current operational state of the interface.
+
+ This leaf has the same semantics as ifOperStatus.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOperStatus";
+ }
+
+ leaf last-change {
+ type yang:date-and-time;
+ status deprecated;
+ description
+ "The time the interface entered its current operational
+ state. If the current state was entered prior to the
+ last re-initialization of the local network management
+ subsystem, then this node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifLastChange";
+ }
+
+ leaf if-index {
+ if-feature if-mib;
+ type int32 {
+ range "1..2147483647";
+ }
+ mandatory true;
+ status deprecated;
+ description
+ "The ifIndex value for the ifEntry represented by this
+ interface.";
+
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifIndex";
+ }
+
+ leaf phys-address {
+ type yang:phys-address;
+ status deprecated;
+ description
+ "The interface's address at its protocol sub-layer. For
+ example, for an 802.x interface, this object normally
+ contains a Media Access Control (MAC) address. The
+ interface's media-specific modules must define the bit
+ and byte ordering and the format of the value of this
+ object. For interfaces that do not have such an address
+ (e.g., a serial line), this node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifPhysAddress";
+ }
+
+ leaf-list higher-layer-if {
+ type interface-state-ref;
+ status deprecated;
+ description
+ "A list of references to interfaces layered on top of this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifStackTable";
+ }
+
+ leaf-list lower-layer-if {
+ type interface-state-ref;
+ status deprecated;
+ description
+ "A list of references to interfaces layered underneath this
+ interface.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifStackTable";
+ }
+
+ leaf speed {
+ type yang:gauge64;
+ units "bits/second";
+ status deprecated;
+ description
+ "An estimate of the interface's current bandwidth in bits
+ per second. For interfaces that do not vary in
+ bandwidth or for those where no accurate estimation can
+
+ be made, this node should contain the nominal bandwidth.
+ For interfaces that have no concept of bandwidth, this
+ node is not present.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifSpeed, ifHighSpeed";
+ }
+
+ container statistics {
+ status deprecated;
+ description
+ "A collection of interface-related statistics objects.";
+
+ leaf discontinuity-time {
+ type yang:date-and-time;
+ mandatory true;
+ status deprecated;
+ description
+ "The time on the most recent occasion at which any one or
+ more of this interface's counters suffered a
+ discontinuity. If no such discontinuities have occurred
+ since the last re-initialization of the local management
+ subsystem, then this node contains the time the local
+ management subsystem re-initialized itself.";
+ }
+
+ leaf in-octets {
+ type yang:counter64;
+ status deprecated;
+ description
+ "The total number of octets received on the interface,
+ including framing characters.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCInOctets";
+ }
+
+ leaf in-unicast-pkts {
+ type yang:counter64;
+ status deprecated;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were not addressed to a
+ multicast or broadcast address at this sub-layer.
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts";
+ }
+
+ leaf in-broadcast-pkts {
+ type yang:counter64;
+ status deprecated;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were addressed to a broadcast
+ address at this sub-layer.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCInBroadcastPkts";
+ }
+
+ leaf in-multicast-pkts {
+ type yang:counter64;
+ status deprecated;
+ description
+ "The number of packets, delivered by this sub-layer to a
+ higher (sub-)layer, that were addressed to a multicast
+ address at this sub-layer. For a MAC-layer protocol,
+ this includes both Group and Functional addresses.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCInMulticastPkts";
+ }
+
+ leaf in-discards {
+ type yang:counter32;
+ status deprecated;
+
+ description
+ "The number of inbound packets that were chosen to be
+ discarded even though no errors had been detected to
+ prevent their being deliverable to a higher-layer
+ protocol. One possible reason for discarding such a
+ packet could be to free up buffer space.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInDiscards";
+ }
+
+ leaf in-errors {
+ type yang:counter32;
+ status deprecated;
+ description
+ "For packet-oriented interfaces, the number of inbound
+ packets that contained errors preventing them from being
+ deliverable to a higher-layer protocol. For character-
+ oriented or fixed-length interfaces, the number of
+ inbound transmission units that contained errors
+ preventing them from being deliverable to a higher-layer
+ protocol.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInErrors";
+ }
+
+ leaf in-unknown-protos {
+ type yang:counter32;
+ status deprecated;
+ description
+ "For packet-oriented interfaces, the number of packets
+ received via the interface that were discarded because
+ of an unknown or unsupported protocol. For
+ character-oriented or fixed-length interfaces that
+ support protocol multiplexing, the number of
+ transmission units received via the interface that were
+ discarded because of an unknown or unsupported protocol.
+ For any interface that does not support protocol
+ multiplexing, this counter is not present.
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos";
+ }
+
+ leaf out-octets {
+ type yang:counter64;
+ status deprecated;
+ description
+ "The total number of octets transmitted out of the
+ interface, including framing characters.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCOutOctets";
+ }
+
+ leaf out-unicast-pkts {
+ type yang:counter64;
+ status deprecated;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted and that were not addressed
+ to a multicast or broadcast address at this sub-layer,
+ including those that were discarded or not sent.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts";
+ }
+
+ leaf out-broadcast-pkts {
+ type yang:counter64;
+ status deprecated;
+
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted and that were addressed to a
+ broadcast address at this sub-layer, including those
+ that were discarded or not sent.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCOutBroadcastPkts";
+ }
+
+ leaf out-multicast-pkts {
+ type yang:counter64;
+ status deprecated;
+ description
+ "The total number of packets that higher-level protocols
+ requested be transmitted and that were addressed to a
+ multicast address at this sub-layer, including those
+ that were discarded or not sent. For a MAC-layer
+ protocol, this includes both Group and Functional
+ addresses.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB -
+ ifHCOutMulticastPkts";
+ }
+
+ leaf out-discards {
+ type yang:counter32;
+ status deprecated;
+ description
+ "The number of outbound packets that were chosen to be
+ discarded even though no errors had been detected to
+ prevent their being transmitted. One possible reason
+ for discarding such a packet could be to free up buffer
+ space.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOutDiscards";
+ }
+
+ leaf out-errors {
+ type yang:counter32;
+ status deprecated;
+ description
+ "For packet-oriented interfaces, the number of outbound
+ packets that could not be transmitted because of errors.
+ For character-oriented or fixed-length interfaces, the
+ number of outbound transmission units that could not be
+ transmitted because of errors.
+
+ Discontinuities in the value of this counter can occur
+ at re-initialization of the management system and at
+ other times as indicated by the value of
+ 'discontinuity-time'.";
+ reference
+ "RFC 2863: The Interfaces Group MIB - ifOutErrors";
+ }
+ }
+ }
+ }
+}
diff --git a/src/share/yang/modules/ietf-yang-types@2013-07-15.yang b/src/share/yang/modules/ietf-yang-types@2013-07-15.yang
new file mode 100644
index 0000000..ee58fa3
--- /dev/null
+++ b/src/share/yang/modules/ietf-yang-types@2013-07-15.yang
@@ -0,0 +1,474 @@
+module ietf-yang-types {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-yang-types";
+ prefix "yang";
+
+ organization
+ "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netmod/>
+ WG List: <mailto:netmod@ietf.org>
+
+ WG Chair: David Kessens
+ <mailto:david.kessens@nsn.com>
+
+ WG Chair: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>
+
+ Editor: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>";
+
+ description
+ "This module contains a collection of generally useful derived
+ YANG data types.
+
+ Copyright (c) 2013 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 6991; see
+ the RFC itself for full legal notices.";
+
+ revision 2013-07-15 {
+ description
+ "This revision adds the following new data types:
+ - yang-identifier
+ - hex-string
+ - uuid
+ - dotted-quad";
+ reference
+ "RFC 6991: Common YANG Data Types";
+ }
+
+ revision 2010-09-24 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 6021: Common YANG Data Types";
+ }
+
+ /*** collection of counter and gauge types ***/
+
+ typedef counter32 {
+ type uint32;
+ description
+ "The counter32 type represents a non-negative integer
+ that monotonically increases until it reaches a
+ maximum value of 2^32-1 (4294967295 decimal), when it
+ wraps around and starts increasing again from zero.
+
+ Counters have no defined 'initial' value, and thus, a
+ single value of a counter has (in general) no information
+ content. Discontinuities in the monotonically increasing
+ value normally occur at re-initialization of the
+ management system, and at other times as specified in the
+ description of a schema node using this type. If such
+ other times can occur, for example, the creation of
+ a schema node of type counter32 at times other than
+ re-initialization, then a corresponding schema node
+ should be defined, with an appropriate type, to indicate
+ the last discontinuity.
+
+ The counter32 type should not be used for configuration
+ schema nodes. A default statement SHOULD NOT be used in
+ combination with the type counter32.
+
+ In the value set and its semantics, this type is equivalent
+ to the Counter32 type of the SMIv2.";
+ reference
+ "RFC 2578: Structure of Management Information Version 2
+ (SMIv2)";
+ }
+
+ typedef zero-based-counter32 {
+ type yang:counter32;
+ default "0";
+ description
+ "The zero-based-counter32 type represents a counter32
+ that has the defined 'initial' value zero.
+
+ A schema node of this type will be set to zero (0) on creation
+ and will thereafter increase monotonically until it reaches
+ a maximum value of 2^32-1 (4294967295 decimal), when it
+ wraps around and starts increasing again from zero.
+
+ Provided that an application discovers a new schema node
+ of this type within the minimum time to wrap, it can use the
+ 'initial' value as a delta. It is important for a management
+ station to be aware of this minimum time and the actual time
+ between polls, and to discard data if the actual time is too
+ long or there is no defined minimum time.
+
+ In the value set and its semantics, this type is equivalent
+ to the ZeroBasedCounter32 textual convention of the SMIv2.";
+ reference
+ "RFC 4502: Remote Network Monitoring Management Information
+ Base Version 2";
+ }
+
+ typedef counter64 {
+ type uint64;
+ description
+ "The counter64 type represents a non-negative integer
+ that monotonically increases until it reaches a
+ maximum value of 2^64-1 (18446744073709551615 decimal),
+ when it wraps around and starts increasing again from zero.
+
+ Counters have no defined 'initial' value, and thus, a
+ single value of a counter has (in general) no information
+ content. Discontinuities in the monotonically increasing
+ value normally occur at re-initialization of the
+ management system, and at other times as specified in the
+ description of a schema node using this type. If such
+ other times can occur, for example, the creation of
+ a schema node of type counter64 at times other than
+ re-initialization, then a corresponding schema node
+ should be defined, with an appropriate type, to indicate
+ the last discontinuity.
+
+ The counter64 type should not be used for configuration
+ schema nodes. A default statement SHOULD NOT be used in
+ combination with the type counter64.
+
+ In the value set and its semantics, this type is equivalent
+ to the Counter64 type of the SMIv2.";
+ reference
+ "RFC 2578: Structure of Management Information Version 2
+ (SMIv2)";
+ }
+
+ typedef zero-based-counter64 {
+ type yang:counter64;
+ default "0";
+ description
+ "The zero-based-counter64 type represents a counter64 that
+ has the defined 'initial' value zero.
+
+ A schema node of this type will be set to zero (0) on creation
+ and will thereafter increase monotonically until it reaches
+ a maximum value of 2^64-1 (18446744073709551615 decimal),
+ when it wraps around and starts increasing again from zero.
+
+ Provided that an application discovers a new schema node
+ of this type within the minimum time to wrap, it can use the
+ 'initial' value as a delta. It is important for a management
+ station to be aware of this minimum time and the actual time
+ between polls, and to discard data if the actual time is too
+ long or there is no defined minimum time.
+
+ In the value set and its semantics, this type is equivalent
+ to the ZeroBasedCounter64 textual convention of the SMIv2.";
+ reference
+ "RFC 2856: Textual Conventions for Additional High Capacity
+ Data Types";
+ }
+
+ typedef gauge32 {
+ type uint32;
+ description
+ "The gauge32 type represents a non-negative integer, which
+ may increase or decrease, but shall never exceed a maximum
+ value, nor fall below a minimum value. The maximum value
+ cannot be greater than 2^32-1 (4294967295 decimal), and
+ the minimum value cannot be smaller than 0. The value of
+ a gauge32 has its maximum value whenever the information
+ being modeled is greater than or equal to its maximum
+ value, and has its minimum value whenever the information
+ being modeled is smaller than or equal to its minimum value.
+ If the information being modeled subsequently decreases
+ below (increases above) the maximum (minimum) value, the
+ gauge32 also decreases (increases).
+
+ In the value set and its semantics, this type is equivalent
+ to the Gauge32 type of the SMIv2.";
+ reference
+ "RFC 2578: Structure of Management Information Version 2
+ (SMIv2)";
+ }
+
+ typedef gauge64 {
+ type uint64;
+ description
+ "The gauge64 type represents a non-negative integer, which
+ may increase or decrease, but shall never exceed a maximum
+ value, nor fall below a minimum value. The maximum value
+ cannot be greater than 2^64-1 (18446744073709551615), and
+ the minimum value cannot be smaller than 0. The value of
+ a gauge64 has its maximum value whenever the information
+ being modeled is greater than or equal to its maximum
+ value, and has its minimum value whenever the information
+ being modeled is smaller than or equal to its minimum value.
+ If the information being modeled subsequently decreases
+ below (increases above) the maximum (minimum) value, the
+ gauge64 also decreases (increases).
+
+ In the value set and its semantics, this type is equivalent
+ to the CounterBasedGauge64 SMIv2 textual convention defined
+ in RFC 2856";
+ reference
+ "RFC 2856: Textual Conventions for Additional High Capacity
+ Data Types";
+ }
+
+ /*** collection of identifier-related types ***/
+
+ typedef object-identifier {
+ type string {
+ pattern '(([0-1](\.[1-3]?[0-9]))|(2\.(0|([1-9]\d*))))'
+ + '(\.(0|([1-9]\d*)))*';
+ }
+ description
+ "The object-identifier type represents administratively
+ assigned names in a registration-hierarchical-name tree.
+
+ Values of this type are denoted as a sequence of numerical
+ non-negative sub-identifier values. Each sub-identifier
+ value MUST NOT exceed 2^32-1 (4294967295). Sub-identifiers
+ are separated by single dots and without any intermediate
+ whitespace.
+
+ The ASN.1 standard restricts the value space of the first
+ sub-identifier to 0, 1, or 2. Furthermore, the value space
+ of the second sub-identifier is restricted to the range
+ 0 to 39 if the first sub-identifier is 0 or 1. Finally,
+ the ASN.1 standard requires that an object identifier
+ has always at least two sub-identifiers. The pattern
+ captures these restrictions.
+
+ Although the number of sub-identifiers is not limited,
+ module designers should realize that there may be
+ implementations that stick with the SMIv2 limit of 128
+ sub-identifiers.
+
+ This type is a superset of the SMIv2 OBJECT IDENTIFIER type
+ since it is not restricted to 128 sub-identifiers. Hence,
+ this type SHOULD NOT be used to represent the SMIv2 OBJECT
+ IDENTIFIER type; the object-identifier-128 type SHOULD be
+ used instead.";
+ reference
+ "ISO9834-1: Information technology -- Open Systems
+ Interconnection -- Procedures for the operation of OSI
+ Registration Authorities: General procedures and top
+ arcs of the ASN.1 Object Identifier tree";
+ }
+
+ typedef object-identifier-128 {
+ type object-identifier {
+ pattern '\d*(\.\d*){1,127}';
+ }
+ description
+ "This type represents object-identifiers restricted to 128
+ sub-identifiers.
+
+ In the value set and its semantics, this type is equivalent
+ to the OBJECT IDENTIFIER type of the SMIv2.";
+ reference
+ "RFC 2578: Structure of Management Information Version 2
+ (SMIv2)";
+ }
+
+ typedef yang-identifier {
+ type string {
+ length "1..max";
+ pattern '[a-zA-Z_][a-zA-Z0-9\-_.]*';
+ pattern '.|..|[^xX].*|.[^mM].*|..[^lL].*';
+ }
+ description
+ "A YANG identifier string as defined by the 'identifier'
+ rule in Section 12 of RFC 6020. An identifier must
+ start with an alphabetic character or an underscore
+ followed by an arbitrary sequence of alphabetic or
+ numeric characters, underscores, hyphens, or dots.
+
+ A YANG identifier MUST NOT start with any possible
+ combination of the lowercase or uppercase character
+ sequence 'xml'.";
+ reference
+ "RFC 6020: YANG - A Data Modeling Language for the Network
+ Configuration Protocol (NETCONF)";
+ }
+
+ /*** collection of types related to date and time***/
+
+ typedef date-and-time {
+ type string {
+ pattern '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?'
+ + '(Z|[\+\-]\d{2}:\d{2})';
+ }
+ description
+ "The date-and-time type is a profile of the ISO 8601
+ standard for representation of dates and times using the
+ Gregorian calendar. The profile is defined by the
+ date-time production in Section 5.6 of RFC 3339.
+
+ The date-and-time type is compatible with the dateTime XML
+ schema type with the following notable exceptions:
+
+ (a) The date-and-time type does not allow negative years.
+
+ (b) The date-and-time time-offset -00:00 indicates an unknown
+ time zone (see RFC 3339) while -00:00 and +00:00 and Z
+ all represent the same time zone in dateTime.
+
+ (c) The canonical format (see below) of data-and-time values
+ differs from the canonical format used by the dateTime XML
+ schema type, which requires all times to be in UTC using
+ the time-offset 'Z'.
+
+ This type is not equivalent to the DateAndTime textual
+ convention of the SMIv2 since RFC 3339 uses a different
+ separator between full-date and full-time and provides
+ higher resolution of time-secfrac.
+
+ The canonical format for date-and-time values with a known time
+ zone uses a numeric time zone offset that is calculated using
+ the device's configured known offset to UTC time. A change of
+ the device's offset to UTC time will cause date-and-time values
+ to change accordingly. Such changes might happen periodically
+ in case a server follows automatically daylight saving time
+ (DST) time zone offset changes. The canonical format for
+ date-and-time values with an unknown time zone (usually
+ referring to the notion of local time) uses the time-offset
+ -00:00.";
+ reference
+ "RFC 3339: Date and Time on the Internet: Timestamps
+ RFC 2579: Textual Conventions for SMIv2
+ XSD-TYPES: XML Schema Part 2: Datatypes Second Edition";
+ }
+
+ typedef timeticks {
+ type uint32;
+ description
+ "The timeticks type represents a non-negative integer that
+ represents the time, modulo 2^32 (4294967296 decimal), in
+ hundredths of a second between two epochs. When a schema
+ node is defined that uses this type, the description of
+ the schema node identifies both of the reference epochs.
+
+ In the value set and its semantics, this type is equivalent
+ to the TimeTicks type of the SMIv2.";
+ reference
+ "RFC 2578: Structure of Management Information Version 2
+ (SMIv2)";
+ }
+
+ typedef timestamp {
+ type yang:timeticks;
+ description
+ "The timestamp type represents the value of an associated
+ timeticks schema node at which a specific occurrence
+ happened. The specific occurrence must be defined in the
+ description of any schema node defined using this type. When
+ the specific occurrence occurred prior to the last time the
+ associated timeticks attribute was zero, then the timestamp
+ value is zero. Note that this requires all timestamp values
+ to be reset to zero when the value of the associated timeticks
+ attribute reaches 497+ days and wraps around to zero.
+
+ The associated timeticks schema node must be specified
+ in the description of any schema node using this type.
+
+ In the value set and its semantics, this type is equivalent
+ to the TimeStamp textual convention of the SMIv2.";
+ reference
+ "RFC 2579: Textual Conventions for SMIv2";
+ }
+
+ /*** collection of generic address types ***/
+
+ typedef phys-address {
+ type string {
+ pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?';
+ }
+
+ description
+ "Represents media- or physical-level addresses represented
+ as a sequence octets, each octet represented by two hexadecimal
+ numbers. Octets are separated by colons. The canonical
+ representation uses lowercase characters.
+
+ In the value set and its semantics, this type is equivalent
+ to the PhysAddress textual convention of the SMIv2.";
+ reference
+ "RFC 2579: Textual Conventions for SMIv2";
+ }
+
+ typedef mac-address {
+ type string {
+ pattern '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}';
+ }
+ description
+ "The mac-address type represents an IEEE 802 MAC address.
+ The canonical representation uses lowercase characters.
+
+ In the value set and its semantics, this type is equivalent
+ to the MacAddress textual convention of the SMIv2.";
+ reference
+ "IEEE 802: IEEE Standard for Local and Metropolitan Area
+ Networks: Overview and Architecture
+ RFC 2579: Textual Conventions for SMIv2";
+ }
+
+ /*** collection of XML-specific types ***/
+
+ typedef xpath1.0 {
+ type string;
+ description
+ "This type represents an XPATH 1.0 expression.
+
+ When a schema node is defined that uses this type, the
+ description of the schema node MUST specify the XPath
+ context in which the XPath expression is evaluated.";
+ reference
+ "XPATH: XML Path Language (XPath) Version 1.0";
+ }
+
+ /*** collection of string types ***/
+
+ typedef hex-string {
+ type string {
+ pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?';
+ }
+ description
+ "A hexadecimal string with octets represented as hex digits
+ separated by colons. The canonical representation uses
+ lowercase characters.";
+ }
+
+ typedef uuid {
+ type string {
+ pattern '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-'
+ + '[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';
+ }
+ description
+ "A Universally Unique IDentifier in the string representation
+ defined in RFC 4122. The canonical representation uses
+ lowercase characters.
+
+ The following is an example of a UUID in string representation:
+ f81d4fae-7dec-11d0-a765-00a0c91e6bf6
+ ";
+ reference
+ "RFC 4122: A Universally Unique IDentifier (UUID) URN
+ Namespace";
+ }
+
+ typedef dotted-quad {
+ type string {
+ pattern
+ '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}'
+ + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
+ }
+ description
+ "An unsigned 32-bit number expressed in the dotted-quad
+ notation, i.e., four octets written as decimal numbers
+ and separated with the '.' (full stop) character.";
+ }
+}
diff --git a/src/share/yang/modules/kea-ctrl-agent@2019-08-12.yang b/src/share/yang/modules/kea-ctrl-agent@2019-08-12.yang
new file mode 100644
index 0000000..ff25896
--- /dev/null
+++ b/src/share/yang/modules/kea-ctrl-agent@2019-08-12.yang
@@ -0,0 +1,78 @@
+module kea-ctrl-agent {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:kea-ctrl-agent";
+ prefix "kea-ctrl-agent";
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import kea-types {
+ prefix kea;
+ revision-date 2019-08-12;
+ }
+
+ organization "Internet Systems Consortium";
+ contact "kea-dev@lists.isc.org";
+ description "This model defines a YANG data model that can be
+ used to configure and manage a Kea control agent.";
+
+ revision 2019-08-12 {
+ description "Initial revision";
+ reference "";
+ }
+
+ /*
+ * Data Nodes
+ */
+
+ container config {
+ // config true;
+ description "Contains control agent configuration.";
+
+ leaf http-host {
+ type inet:ip-address;
+ description "IP address to which HTTP service will be bound.";
+ }
+
+ leaf http-port {
+ type uint16;
+ description "Port to which HTTP service will be bound.";
+ }
+
+ container control-sockets {
+ description "Control sockets.";
+ list socket {
+ key server-type;
+ description "List of server control socket.";
+ leaf server-type {
+ type enumeration {
+ enum "dhcp4" {
+ description "kea-dhcp4 server";
+ }
+ enum "dhcp6" {
+ description "kea-dhcp6 server";
+ }
+ enum "d2" {
+ description "kea-dhcp-ddns server";
+ }
+ }
+ mandatory true;
+ description "Server type.";
+ }
+ container control-socket {
+ description "Control socket information.";
+ uses kea:control-socket-info;
+ }
+ }
+ }
+
+ uses kea:hooks-libraries;
+
+ leaf user-context {
+ type kea:user-context;
+ description "Control agent user context.";
+ }
+
+ uses kea:loggers;
+ }
+}
diff --git a/src/share/yang/modules/kea-dhcp-ddns@2022-07-27.yang b/src/share/yang/modules/kea-dhcp-ddns@2022-07-27.yang
new file mode 100644
index 0000000..bafa6a3
--- /dev/null
+++ b/src/share/yang/modules/kea-dhcp-ddns@2022-07-27.yang
@@ -0,0 +1,184 @@
+module kea-dhcp-ddns {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:kea-dhcp-ddns";
+ prefix "kea-dhcp-ddns";
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import kea-types {
+ prefix kea;
+ revision-date 2019-08-12;
+ }
+
+ organization "Internet Systems Consortium";
+ contact "kea-dev@lists.isc.org";
+ description "This model defines a YANG data model that can be
+ used to configure and manage a Kea DHCP-DDNS server.";
+
+ revision 2022-07-27 {
+ description "Added GSS-TSIG key-name configration parameter for DNS
+ servers";
+ }
+
+ revision 2019-08-12 {
+ description "Initial revision";
+ reference "";
+ }
+
+ /*
+ * Groupings
+ */
+
+ grouping managed-domains {
+ description "Contains parameters for forward or reverse DDNS managed
+ domains.";
+
+ container ddns-domains {
+ description "DDNS domains.";
+ list ddns-domain {
+ key name;
+ description "List of DDNS domains.";
+ leaf name {
+ type string;
+ mandatory true;
+ description "DDNS domain name.";
+ }
+ leaf key-name {
+ type string;
+ description "TSIG key to use. Blank means no TSIG.";
+ }
+ container dns-servers {
+ description "DNS servers.";
+ list server {
+ key ip-address;
+ description "List of DNS servers.";
+ leaf hostname {
+ type string;
+ description "DNS server hostname.";
+ }
+ leaf ip-address {
+ type inet:ip-address;
+ mandatory true;
+ description "DNS server IP address.";
+ }
+ leaf port {
+ type uint16;
+ description "DNS server port.";
+ }
+ leaf key-name {
+ type string;
+ description "Name of the TSIG key used to protect DNS updates for
+ this server.";
+ }
+ leaf user-context {
+ type kea:user-context;
+ description "DNS server user context.";
+ }
+ }
+ }
+ leaf user-context {
+ type kea:user-context;
+ description "DDNS domain user context.";
+ }
+ }
+ }
+ }
+
+ /*
+ * Data Nodes
+ */
+
+ container config {
+ // config true;
+ description "Contains DHCP-DDNS server configuration.";
+
+ leaf ip-address {
+ type inet:ip-address;
+ description "IP address on which the server listens for requests.";
+ }
+
+ leaf port {
+ type uint16;
+ description "Port on which the server listens for requests.";
+ }
+
+ leaf dns-server-timeout {
+ type uint32;
+ units "milliseconds";
+ description "Maximum amount of time that the server will wait for
+ a response from a DNS server to a single DNS update message.";
+ }
+
+ leaf ncr-protocol {
+ type enumeration {
+ enum "UDP" {
+ description "UDP transport";
+ }
+ enum "TCP" {
+ description "TCP transport";
+ }
+ }
+ description "Protocol to use when sending requests to the server.";
+ }
+
+ leaf ncr-format {
+ type enumeration {
+ enum "JSON" {
+ description "JSON format";
+ }
+ }
+ description "Packet format to use when sending requests to the server.";
+ }
+
+ uses kea:control-socket;
+
+ container forward-ddns {
+ description "Forward DNS zones.";
+ uses managed-domains;
+ }
+
+ container reverse-ddns {
+ description "Reverse DNS zones.";
+ uses managed-domains;
+ }
+
+ container tsig-keys {
+ description "Keys to use with TSIG.";
+ list key {
+ key name;
+ description "List of TSIG keys.";
+ leaf name {
+ type string;
+ mandatory true;
+ description "Key name.";
+ }
+ leaf algorithm {
+ type string;
+ mandatory true;
+ description "Hashing algorithm to use with the key.";
+ }
+ leaf digest-bits {
+ type uint16;
+ units "bits";
+ description "Minimum truncated length. 0 means no truncation.";
+ }
+ leaf secret {
+ type string;
+ mandatory true;
+ description "Shared secret for the key.";
+ }
+ leaf user-context {
+ type kea:user-context;
+ description "Key user context.";
+ }
+ }
+ }
+ leaf user-context {
+ type kea:user-context;
+ description "DHCP-DDNS server user context.";
+ }
+
+ uses kea:loggers;
+ }
+}
diff --git a/src/share/yang/modules/kea-dhcp-types@2022-07-27.yang b/src/share/yang/modules/kea-dhcp-types@2022-07-27.yang
new file mode 100644
index 0000000..68b3ce3
--- /dev/null
+++ b/src/share/yang/modules/kea-dhcp-types@2022-07-27.yang
@@ -0,0 +1,1122 @@
+module kea-dhcp-types {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:kea-dhcp-types";
+ prefix "kea-dhcp-types";
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import kea-types {
+ prefix kea;
+ revision-date 2019-08-12;
+ }
+
+ organization "Internet Systems Consortium";
+ contact "kea-dev@lists.isc.org";
+ description "This file defines some commonly used Kea DHCP types and
+ groupings.";
+
+ revision 2022-07-27 {
+ description "Added TLS parameters for database connections: trust-anchor,
+ cert-file, key-file, cipher-list. Added parameters which govern
+ the way the server behaves regarding detection of configured
+ interfaces: service-sockets-require-all,
+ service-sockets-max-retries, service-sockets-retry-wait-time.
+ Added parameters which govern reservations lookup:
+ early-global-reservations-lookup, reservations-lookup-first.";
+ }
+
+ revision 2021-11-24 {
+ description "Fixed store-extended-info. Added several containers and leaves:
+ compatibility, lenient-option-parsing, multi-threading,
+ enable-multi-threading, packet-queue-size, thread-pool-size,
+ valid-lifetime, min-valid-lifetime, max-valid-lifetime,
+ preferred-lifetime, min-preferred-lifetime,
+ max-preferred-lifetime, cache-max-age, cache-threshold,
+ ddns-generated-prefix, ddns-override-client-update,
+ ddns-override-no-update, ddns-qualifying-suffix,
+ ddns-replace-client-name, ddns-send-updates,
+ ddns-update-on-renew, ddns-use-conflict-resolution,
+ ip-reservations-unique, parked-packet-limit,
+ reservations-global, reservations-in-subnet,
+ reservations-out-of-pool, statistic-default-sample-age,
+ statistic-default-sample-count, store-extended-info, on-fail.";
+ }
+
+ revision 2021-08-25 {
+ description "Added store-extended-info";
+ }
+
+ revision 2019-08-12 {
+ description "Initial revision";
+ }
+
+ /*
+ * Typedef
+ */
+ typedef host-reservation-mode {
+ type enumeration {
+ enum "disabled" {
+ description "Host reservation support is disabled.";
+ }
+ enum "out-of-pool" {
+ description "Allows only out of pool host reservations.";
+ }
+ enum "all" {
+ description "Allows both in pool and out of pool host reservations.";
+ }
+ enum "global" {
+ description "Allows only global host reservations.";
+ }
+ }
+ description "Host reservation mode.";
+ }
+
+ typedef lease-state {
+ type enumeration {
+ enum "default" {
+ description "Active/default";
+ }
+ enum "declined" {
+ description "Declined";
+ }
+ enum "expired-reclaimed" {
+ description "Expired-reclaimed";
+ }
+ }
+ description "Defines state of the lease.";
+ }
+
+ /*
+ * Grouping
+ */
+ grouping valid-lifetime {
+ description "Valid lifetime grouping.";
+ leaf valid-lifetime {
+ type uint32;
+ units "seconds";
+ description "Valid lifetime entry.";
+ }
+ }
+
+ grouping min-valid-lifetime {
+ description "Minimum valid lifetime grouping.";
+ leaf min-valid-lifetime {
+ type uint32;
+ units "seconds";
+ description "Minimum valid lifetime entry.";
+ }
+ }
+
+ grouping max-valid-lifetime {
+ description "Maximum valid lifetime grouping.";
+ leaf max-valid-lifetime {
+ type uint32;
+ units "seconds";
+ description "Maximum valid lifetime entry.";
+ }
+ }
+
+ grouping renew-timer {
+ description "Renew timer grouping.";
+ leaf renew-timer {
+ type uint32;
+ units "seconds";
+ description "Renew timer entry.";
+ }
+ }
+
+ grouping rebind-timer {
+ description "Rebind timer grouping.";
+ leaf rebind-timer {
+ type uint32;
+ units "seconds";
+ description "Rebind timer entry.";
+ }
+ }
+
+ grouping calculate-tee-times {
+ description "Calculate T1/T2 grouping.";
+ leaf calculate-tee-times {
+ type boolean;
+ description "Calculate T1/T2 entry.";
+ }
+ }
+
+ grouping cache-max-age {
+ leaf cache-max-age {
+ type uint32;
+ description
+ "maximum lease age until cached";
+ }
+ }
+
+ grouping cache-threshold {
+ leaf cache-threshold {
+ type decimal64 {
+ fraction-digits 18;
+ range "0 .. 1";
+ }
+ description
+ "cache threshold";
+ }
+ }
+
+ grouping t1-percent {
+ description "T1 timer grouping.";
+ leaf t1-percent {
+ type decimal64 {
+ fraction-digits 18;
+ range "0 .. 1";
+ }
+ description "T1 timer entry.";
+ }
+ }
+
+ grouping t2-percent {
+ description "T2 timer grouping.";
+ leaf t2-percent {
+ type decimal64 {
+ fraction-digits 18;
+ range "0 .. 1";
+ }
+ description "T2 timer entry.";
+ }
+ }
+
+ grouping database {
+ description "Database grouping.";
+ leaf database-type {
+ type string;
+ mandatory true;
+ description "Database type (e.g. mysql).";
+ }
+ leaf user {
+ type string;
+ description "Database user name.";
+ }
+ leaf password {
+ type string;
+ description "Database user password.";
+ }
+ leaf host {
+ type string;
+ description "Database host.";
+ }
+ leaf name {
+ type string;
+ description "Database name.";
+ }
+ leaf persist {
+ type boolean;
+ description "Write lease to disk file. This parameter applies only to
+ memfile backend.";
+ }
+ leaf port {
+ type uint16;
+ description "Database port.";
+ }
+ leaf lfc-interval {
+ type uint32;
+ units "seconds";
+ description "Interval between two lease file cleanups.";
+ }
+ leaf readonly {
+ type boolean;
+ description "If set to true, the database will be connected in
+ read-only mode. This does not make sense for lease
+ databases, only for host reservations and possibly
+ for upcoming config backend.";
+ }
+ leaf trust-anchor {
+ type string;
+ description "TLS trust anchor.";
+ }
+ leaf cert-file {
+ type string;
+ description "TLS certificate file.";
+ }
+ leaf key-file {
+ type string;
+ description "TLS key file.";
+ }
+ leaf cipher-list {
+ type string;
+ description "TLS cipher list.";
+ }
+ leaf connect-timeout {
+ type uint32;
+ units "milliseconds";
+ description "Database connection timeout.";
+ }
+ leaf contact-points {
+ type string;
+ description "Cassandra database contact points, a coma separated list of
+ IP addresses.";
+ }
+ leaf keyspace {
+ type string;
+ description "Cassandra database keyspace (this is Cassandra's equivalent
+ of a database name).";
+ }
+ leaf max-reconnect-tries {
+ type uint32;
+ description "Maximum of recovery attempts before exit.";
+ }
+ leaf reconnect-wait-time {
+ type uint32;
+ units "milliseconds";
+ description "Waiting delay between two recovery attempts.";
+ }
+ leaf request-timeout {
+ type uint32;
+ units "milliseconds";
+ description "Timeout waiting for a response.";
+ }
+ leaf tcp-keepalive {
+ type uint32;
+ units "seconds";
+ description "TCP keepalive for the database connection.";
+ }
+ leaf tcp-nodelay {
+ type boolean;
+ description "TCP nodelay for the database connection.";
+ }
+ leaf consistency {
+ type string;
+ description "Consistency level.";
+ }
+ leaf serial-consistency {
+ type string;
+ description "Serial consistency level.";
+ }
+ leaf max-row-errors {
+ type uint32;
+ description "Maximum row errors (0 means unlimited).";
+ }
+ leaf on-fail {
+ type string;
+ description
+ "action to take when connection recovery fails";
+ }
+ uses kea:user-context {
+ refine user-context {
+ description "Database user context. Arbitrary JSON data can be
+ stored here.";
+ }
+ }
+ }
+
+ grouping databases {
+ description "Databases grouping.";
+ container lease-database {
+ presence "Have lease database.";
+ description "Lease database.";
+ uses database;
+ }
+ list hosts-database {
+ key database-type;
+ description "List of databases.";
+ uses database;
+ }
+ }
+
+ grouping expired-leases-processing {
+ description "Expired leases processing grouping.";
+ container expired-leases-processing {
+ description "Expired leases processing setup.";
+ leaf reclaim-timer-wait-time {
+ type uint32;
+ units "seconds";
+ description "Interval between reclamation cycles.";
+ }
+ leaf flush-reclaimed-timer-wait-time {
+ type uint32;
+ units "seconds";
+ description "Interval between reclaimed leases collection.";
+ }
+ leaf hold-reclaimed-time {
+ type uint32;
+ units "seconds";
+ description "Hold timer for re-assignment.";
+ }
+ leaf max-reclaim-leases {
+ type uint32;
+ description "Maximum number of reclaimed leases per cycle.";
+ }
+ leaf max-reclaim-time {
+ type uint32;
+ units "milliseconds";
+ description "Maximum duration of a reclamation cycle.";
+ }
+ leaf unwarned-reclaim-cycles {
+ type uint32;
+ description "Maximum numbers of uncomplete cycles before warning.";
+ }
+ }
+ }
+
+ grouping hostname-char-set {
+ description "Hostname character set grouping.";
+ leaf hostname-char-set {
+ type string;
+ description "A regex defining invalid characters. If detected, those
+ will be replaced by hostname-char-replacement.";
+ }
+ }
+
+ grouping hostname-char-replacement {
+ description "Hostname character replacement grouping.";
+ leaf hostname-char-replacement {
+ type string;
+ description "Replacement for invalid charaters. See
+ hostname-char-set.";
+ }
+ }
+
+ grouping dhcp-ddns {
+ description "DHCP-DDNS grouping.";
+ container dhcp-ddns {
+ description "DHCP-DDNS client setup.";
+ leaf enable-updates {
+ type boolean;
+ description "Enable DHCP-DDNS updates.";
+ }
+ leaf qualifying-suffix {
+ type string;
+ description "DHCP-DDNS qualifying suffix.";
+ }
+ leaf server-ip {
+ type inet:ip-address;
+ description "DHCP-DDNS server IP address.";
+ }
+ leaf server-port {
+ type uint16;
+ description "DHCP-DDNS server port.";
+ }
+ leaf sender-ip {
+ type inet:ip-address;
+ description "DHCP-DDNS sender IP address.";
+ }
+ leaf sender-port {
+ type uint16;
+ description "DHCP-DDNS sender port.";
+ }
+ leaf max-queue-size {
+ type uint32;
+ description "Maximum DHCP-DDNS queue size.";
+ }
+ leaf ncr-protocol {
+ type enumeration {
+ enum "UDP" {
+ description "UDP transport";
+ }
+ enum "TCP" {
+ description "TCP transport";
+ }
+ }
+ description "Protocol to use for DHCP-DDNS communication.
+ Currently only UDP is supported.";
+ }
+ leaf ncr-format {
+ type enumeration {
+ enum "JSON" {
+ description "JSON format";
+ }
+ }
+ description "Packet format to use for DHCP-DDNS.";
+ }
+ leaf override-no-update {
+ type boolean;
+ description "Ignore client request and send update.";
+ }
+ leaf override-client-update {
+ type boolean;
+ description "Ignore client delegation.";
+ }
+ leaf replace-client-name {
+ type enumeration {
+ enum "when-present" {
+ description "When the client sent a name.";
+ }
+ enum "never" {
+ description "Never replace or generate a name.";
+ }
+ enum "always" {
+ description "Always replace or generate a name.";
+ }
+ enum "when-not-present" {
+ description "When the client did not send a name.";
+ }
+ }
+ description "Replace the name provided by the client.";
+ }
+ leaf generated-prefix {
+ type string;
+ description "DHCP-DDNS generated prefix.";
+ }
+ uses hostname-char-set;
+ uses hostname-char-replacement;
+ uses kea:user-context {
+ refine user-context {
+ description "DHCP-DDNS user context. Arbitrary JSON data can
+ be stored here.";
+ }
+ }
+ }
+ }
+
+ grouping sanity-checks {
+ description "Sanity checks grouping.";
+ container sanity-checks {
+ description "Sanity checks container.";
+ leaf lease-checks {
+ type enumeration {
+ enum "none" {
+ description "No checks.";
+ }
+ enum "warn" {
+ description "When a check fails print a warning and accept the
+ lease.";
+ }
+ enum "fix" {
+ description "When a check fails try to fix it and accept the
+ lease.";
+ }
+ enum "fix-del" {
+ description "When a check fails try to fix it and reject the
+ lease if still bad.";
+ }
+ enum "del" {
+ description "When a check fails reject the lease.";
+ }
+ }
+ description "Lease checks: verify subnet-id consistency on memfile
+ loading.";
+ }
+ }
+ }
+
+ grouping client-class {
+ description "Client class grouping.";
+ leaf client-class {
+ type string;
+ description "Client class entry.";
+ }
+ }
+
+ grouping pool-client-class {
+ description "Client class grouping for a pool.";
+ uses client-class {
+ refine client-class {
+ description "Pool client class guard (only clients belonging
+ to this class will be allowed in this pool).";
+ }
+ }
+ }
+
+ grouping subnet-client-class {
+ description "Client class grouping for a subnet.";
+ uses client-class {
+ refine client-class {
+ description "Subnet client class guard (only clients belonging to this
+ class will be allowed in this subnet).";
+ }
+ }
+ }
+
+ grouping network-client-class {
+ description "Client class grouping for a shared network.";
+ uses client-class {
+ refine client-class {
+ description "Shared network client class guard (only clients
+ belonging to this class will be allowed in this
+ shared network).";
+ }
+ }
+ }
+
+ grouping require-client-classes {
+ description "Require client classes grouping.";
+ leaf-list require-client-classes {
+ type string;
+ description "List of client classes.";
+ }
+ }
+
+ grouping pool-require-client-classes {
+ description "Require client classes grouping for a pool.";
+ uses require-client-classes {
+ refine require-client-classes {
+ description "Pool require client classes.";
+ }
+ }
+ }
+
+ grouping subnet-require-client-classes {
+ description "Require client classes grouping for a subnet.";
+ uses require-client-classes {
+ refine require-client-classes {
+ description "Subnet require client classes.";
+ }
+ }
+ }
+
+ grouping network-require-client-classes {
+ description "Require client classes grouping for a shared network.";
+ uses require-client-classes {
+ refine require-client-classes {
+ description "Shared network require client classes.";
+ }
+ }
+ }
+
+ grouping interface {
+ description "Interface grouping.";
+ leaf interface {
+ type string;
+ description "Interface entry.";
+ }
+ }
+
+ grouping subnet-interface {
+ description "Interface grouping for a subnet.";
+ uses interface {
+ refine interface {
+ description "Name of the network interface this subnet is directly
+ accessible with (optional).";
+ }
+ }
+ }
+
+ grouping network-interface {
+ description "Interface grouping for a shared network.";
+ uses interface {
+ refine interface {
+ description "Specifies the network interface this shared network is
+ directly accessible with. (optional)";
+ }
+ }
+ }
+
+ grouping subnet-id {
+ description "Subnet ID grouping.";
+ leaf id {
+ type uint32 {
+ range 1..max;
+ }
+ mandatory true;
+ description "Subnet ID, a unique identifier used to locate or reference
+ a subnet.";
+ }
+ }
+
+ grouping host-identifier {
+ description "Host identifier grouping.";
+ leaf identifier {
+ type string;
+ description "Host identifier.";
+ }
+ }
+
+ grouping host-hostname {
+ description "Host DNS name grouping.";
+ leaf hostname {
+ type string;
+ description "Host DNS name.";
+ }
+ }
+
+ grouping host-client-classes {
+ description "Host client classes grouping.";
+ leaf-list client-classes {
+ type string;
+ description "Host client classes (if host identifier matches, a
+ client's packet will be added to the classes liste
+ here.)";
+ }
+ }
+
+ grouping host-subnet-id {
+ description "Host subnet ID grouping.";
+ leaf subnet-id {
+ type uint32;
+ mandatory true;
+ description "Host subnet ID.";
+ }
+ }
+
+ grouping reservation-mode {
+ description "Reservation mode grouping.";
+ leaf reservation-mode {
+ type host-reservation-mode;
+ description "Reservation mode entry.";
+ }
+ }
+
+ grouping interfaces-re-detect {
+ description "Interfaces re-detect grouping.";
+ leaf re-detect {
+ type boolean;
+ description "Re-detect interfaces at each reconfiguration.";
+ }
+ }
+
+ grouping interfaces-service-sockets-require-all {
+ description "Interfaces service-sockets-require-all grouping.";
+ leaf service-sockets-require-all {
+ type boolean;
+ description "Kea tries to bind the service sockets during initialization.
+ It may fail due to a port being already opened or
+ misconfiguration. Kea can suppress these errors and only logs
+ them. This flag prevents starting the DHCP server without
+ binding all sockets.";
+ }
+ }
+
+ grouping interfaces-service-sockets-max-retries {
+ description "Interfaces service-sockets-max-retries grouping.";
+ leaf service-sockets-max-retries {
+ type uint32;
+ description "Kea tries to bind the service sockets during initialization.
+ This option specifies how many times binding to interface
+ will be retried.";
+ }
+ }
+
+ grouping interfaces-service-sockets-retry-wait-time {
+ description "Interfaces service-sockets-retry-wait-time grouping.";
+ leaf service-sockets-retry-wait-time {
+ type uint32;
+ description "The time interval in milliseconds to wait before the next
+ attempt to retry opening a service socket.";
+ }
+ }
+
+ grouping class-name {
+ description "Client class name grouping.";
+ leaf name {
+ type string;
+ mandatory true;
+ description "Name of the client class.";
+ }
+ }
+
+ grouping class-test {
+ description "Client class test grouping.";
+ leaf test {
+ type string;
+ description "Defines an expression that evaluates every incoming
+ packet.";
+ }
+ }
+
+ grouping class-only-if-required {
+ description "Client class only-if-required grouping.";
+ leaf only-if-required {
+ type boolean;
+ description "Client class only if required flag.";
+ }
+ }
+
+ grouping option-def-name {
+ description "Option definition name grouping.";
+ leaf name {
+ type string;
+ mandatory true;
+ description "Name of the new option being defined.";
+ }
+ }
+
+ grouping option-def-type {
+ description "Option definition type grouping.";
+ leaf type {
+ type string;
+ mandatory true;
+ description "Type of the new option being defined (such as
+ string, record or uint8).";
+ }
+ }
+
+ grouping option-def-record-types {
+ description "Option definition record types grouping.";
+ leaf record-types {
+ type string;
+ description "Option definition record types.";
+ }
+ }
+
+ grouping option-def-encapsulate {
+ description "Option definition encapsulate grouping.";
+ leaf encapsulate {
+ type string;
+ description "Defines option space this new option encapsulates.
+ Usually empty.";
+ }
+ }
+
+ grouping option-data-name {
+ description "Option data name grouping.";
+ leaf name {
+ type string;
+ description "Option name.";
+ }
+ }
+
+ grouping option-data-data {
+ description "Option data data grouping.";
+ leaf data {
+ type string;
+ description "Option data.";
+ }
+ }
+
+ grouping option-data-csv-format {
+ description "Option data csv-format grouping.";
+ leaf csv-format {
+ type boolean;
+ description "If true, the option is specified as comma separated
+ values. If false, it is expected as a hex string.";
+ }
+ }
+
+ grouping option-data-always-send {
+ description "Option data always-send grouping.";
+ leaf always-send {
+ type boolean;
+ description "Defines whether to always send the option,
+ regardless if the client requested it or not.";
+ }
+ }
+
+ grouping option-def-array {
+ description "Option data array grouping.";
+ leaf array {
+ type boolean;
+ description "Option definition array flag.";
+ }
+ }
+
+ grouping decline-probation-period {
+ description "Decline probation period grouping.";
+ leaf decline-probation-period {
+ type uint32;
+ units "seconds";
+ description "Decline probabation period.";
+ }
+ }
+
+ grouping network-name {
+ description "Shared network name grouping.";
+ leaf name {
+ type string;
+ mandatory true;
+ description "Shared network name.";
+ }
+ }
+
+ grouping dhcp4o6-port {
+ description "DHCPv4-over-DHCPv6 port grouping.";
+ leaf dhcp4o6-port {
+ type uint16;
+ description "DHCPv4-over-DHCPv6 interserver port.";
+ }
+ }
+
+ grouping pool-user-context {
+ description "User context grouping for a pool.";
+ uses kea:user-context {
+ refine user-context {
+ description "Pool user context. Arbitrary JSON data can be
+ stored here.";
+ }
+ }
+ }
+
+ grouping host-user-context {
+ description "User context grouping for a host reservation.";
+ uses kea:user-context {
+ refine user-context {
+ description "Host user context. Arbitrary JSON data can be
+ stored here.";
+ }
+ }
+ }
+
+ grouping subnet-user-context {
+ description "User context grouping for a subnet.";
+ uses kea:user-context {
+ refine user-context {
+ description "Subnet user context. Arbitrary JSON data can be
+ stored here.";
+ }
+ }
+ }
+
+ grouping network-user-context {
+ description "User context grouping for a shared network.";
+ uses kea:user-context {
+ refine user-context {
+ description "Shared network user context. Arbitrary JSON data can be
+ stored here.";
+ }
+ }
+ }
+
+ grouping interfaces-user-context {
+ description "User context grouping for interfaces.";
+ uses kea:user-context {
+ refine user-context {
+ description "Interfaces user context. Arbitrary JSON data can
+ be stored here.";
+ }
+ }
+ }
+
+ grouping class-user-context {
+ description "User context grouping for a client class.";
+ uses kea:user-context {
+ refine user-context {
+ description "Client class user context. Arbitrary JSON data can
+ be stored here.";
+ }
+ }
+ }
+
+ grouping option-def-user-context {
+ description "User context grouping for an option definition.";
+ uses kea:user-context {
+ refine user-context {
+ description "Option definition user context. Arbitrary JSON data
+ can be stored here.";
+ }
+ }
+ }
+
+ grouping option-data-user-context {
+ description "User context grouping for an option data.";
+ uses kea:user-context {
+ refine user-context {
+ description "Option user context. Arbitrary JSON data can be
+ stored here.";
+ }
+ }
+ }
+
+ grouping config-control {
+ description "Config control grouping.";
+ container config-control {
+ description "Config control.";
+ leaf config-fetch-wait-time {
+ type uint32;
+ units "seconds";
+ description "Configuration fetch wait time entry.";
+ }
+ list config-database {
+ key database-type;
+ description "List of databases.";
+ uses database;
+ }
+ }
+ }
+
+ grouping server-tag {
+ description "Server tag grouping.";
+ leaf server-tag {
+ type string;
+ description "Server tag.";
+ }
+ }
+
+ grouping dhcp-queue-control {
+ description "DHCP queue control grouping.";
+ leaf dhcp-queue-control {
+ type string;
+ description "DHCP queue control (JSON map).";
+ }
+ }
+
+ grouping store-extended-info {
+ leaf store-extended-info {
+ description "whether Kea should store additional client query data e.g. RAI in packets";
+ type boolean;
+ }
+ }
+
+ grouping compatibility {
+ container compatibility {
+ description
+ "parameters for triggering behaviors compatible with broken or
+ non-compliant clients, relays or other agents";
+ leaf lenient-option-parsing {
+ type boolean;
+ description
+ "enable more lenient option parsing";
+ }
+ }
+ }
+
+ grouping ddns-generated-prefix {
+ leaf ddns-generated-prefix {
+ type string;
+ description
+ "the prefix to be prepended to the generated Client FQDN";
+ }
+ }
+
+ grouping ddns-override-client-update {
+ leaf ddns-override-client-update {
+ type boolean;
+ description
+ "whether to ignore DHCP client requests to update DNS";
+ }
+ }
+
+ grouping ddns-override-no-update {
+ leaf ddns-override-no-update {
+ type boolean;
+ description
+ "whether to override DHCP client requests to not update DNS";
+ }
+ }
+
+ grouping ddns-qualifying-suffix {
+ leaf ddns-qualifying-suffix {
+ type string;
+ description
+ "the suffix appended to the partial name sent to the DNS";
+ }
+ }
+
+ grouping ddns-replace-client-name {
+ leaf ddns-replace-client-name {
+ type string;
+ description
+ "whether the server should honor the hostname or Client FQDN
+ sent by the client; multiple options are available";
+ }
+ }
+
+ grouping ddns-send-updates {
+ leaf ddns-send-updates {
+ type boolean;
+ description
+ "whether DDNS updates should be enabled";
+ }
+ }
+
+ grouping ddns-update-on-renew {
+ leaf ddns-update-on-renew {
+ type boolean;
+ description
+ "whether to update DNS when leases are renewed";
+ }
+ }
+
+ grouping ddns-use-conflict-resolution {
+ leaf ddns-use-conflict-resolution {
+ type boolean;
+ description
+ "whether RFC 4703 DNS update conflict resolution should be
+ employed";
+ }
+ }
+
+ grouping ip-reservations-unique {
+ leaf ip-reservations-unique {
+ type boolean;
+ description
+ "whether Kea should enforce a single reservation per address";
+ }
+ }
+
+ grouping early-global-reservations-lookup {
+ leaf early-global-reservations-lookup {
+ type boolean;
+ description
+ "whether an early global host reservations lookup should be performed";
+ }
+ }
+
+ grouping reservations-lookup-first {
+ leaf reservations-lookup-first {
+ type boolean;
+ description
+ "whether host reservations lookup should be performed before lease
+ lookup";
+ }
+ }
+
+ grouping multi-threading {
+ container multi-threading {
+ description
+ "multi-threading parameters";
+ leaf enable-multi-threading {
+ type boolean;
+ description
+ "whether Kea should run on multiple threads or just one";
+ }
+
+ leaf packet-queue-size {
+ type uint32;
+ description
+ "the capacity of the packet backlog that threads dequeue from";
+ }
+
+ leaf thread-pool-size {
+ type uint32;
+ description
+ "the number of threads Kea should use for packet processing";
+ }
+ }
+ }
+
+ grouping parked-packet-limit {
+ leaf parked-packet-limit {
+ type uint32;
+ description
+ "maximum allowed capacity of parked packets";
+ }
+ }
+
+ grouping reservations-global {
+ leaf reservations-global {
+ type boolean;
+ description
+ "whether Kea should lookup global reservations";
+ }
+ }
+
+ grouping reservations-in-subnet {
+ leaf reservations-in-subnet {
+ type boolean;
+ description
+ "whether Kea should lookup in-subnet reservations";
+ }
+ }
+
+ grouping reservations-out-of-pool {
+ leaf reservations-out-of-pool {
+ type boolean;
+ description
+ "whether Kea can assume that all reservations are
+ out-of-pool";
+ }
+ }
+
+ grouping statistic-default-sample-age {
+ leaf statistic-default-sample-age {
+ type uint32;
+ description
+ "the maximum number of seconds that samples are kept for";
+ }
+ }
+
+ grouping statistic-default-sample-count {
+ leaf statistic-default-sample-count {
+ type uint32;
+ description
+ "the maximum number of samples which are kept";
+ }
+ }
+}
diff --git a/src/share/yang/modules/kea-dhcp4-server@2022-07-27.yang b/src/share/yang/modules/kea-dhcp4-server@2022-07-27.yang
new file mode 100644
index 0000000..de56557
--- /dev/null
+++ b/src/share/yang/modules/kea-dhcp4-server@2022-07-27.yang
@@ -0,0 +1,609 @@
+module kea-dhcp4-server {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:kea-dhcp4-server";
+ prefix "kea-dhcp4-server";
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import kea-types {
+ prefix kea;
+ revision-date 2019-08-12;
+ }
+ import kea-dhcp-types {
+ prefix dhcp;
+ revision-date 2022-07-27;
+ }
+
+ organization "Internet Systems Consortium";
+ contact "kea-dev@lists.isc.org";
+ description "This model defines a YANG data model that can be
+ used to configure and manage a Kea DHCPv4 server.";
+
+ revision 2022-07-27 {
+ description "Added TLS parameters for database connections: trust-anchor,
+ cert-file, key-file, cipher-list. Added parameters which govern
+ the way the server behaves regarding detection of configured
+ interfaces: service-sockets-require-all,
+ service-sockets-max-retries, service-sockets-retry-wait-time.
+ Added parameters which govern reservations lookup:
+ early-global-reservations-lookup, reservations-lookup-first.";
+ }
+
+ revision 2021-11-24 {
+ description "Fixed store-extended-info. Added several containers and leaves:
+ compatibility, lenient-option-parsing, multi-threading,
+ enable-multi-threading, packet-queue-size, thread-pool-size,
+ valid-lifetime, min-valid-lifetime, max-valid-lifetime,
+ preferred-lifetime, min-preferred-lifetime,
+ max-preferred-lifetime, cache-max-age, cache-threshold,
+ ddns-generated-prefix, ddns-override-client-update,
+ ddns-override-no-update, ddns-qualifying-suffix,
+ ddns-replace-client-name, ddns-send-updates,
+ ddns-update-on-renew, ddns-use-conflict-resolution,
+ ip-reservations-unique, parked-packet-limit,
+ reservations-global, reservations-in-subnet,
+ reservations-out-of-pool, statistic-default-sample-age,
+ statistic-default-sample-count, store-extended-info, on-fail.";
+ }
+
+ revision 2021-08-25 {
+ description "Added store-extended-info";
+ }
+
+ revision 2019-08-12 {
+ description "Initial revision";
+ }
+
+ /*
+ * Typedefs
+ */
+ typedef host-identifier-type {
+ type enumeration {
+ enum "duid" {
+ description "DUID";
+ }
+ enum "hw-address" {
+ description "Hardware address";
+ }
+ enum "circuit-id" {
+ description "Circuit-id option";
+ }
+ enum "client-id" {
+ description "Client identifier";
+ }
+ enum "flex-id" {
+ description "Flexible identifier";
+ }
+ }
+ description "Host identifier type.";
+ }
+
+ /*
+ * Groupings
+ */
+ grouping match-client-id {
+ description "Match client ID grouping.";
+ leaf match-client-id {
+ type boolean;
+ description "Use client-id for lease lookups. If set to false, client-id
+ will be ignored.";
+ }
+ }
+
+ grouping next-server {
+ description "Next server address grouping.";
+ leaf next-server {
+ type inet:ipv4-address;
+ description "Next server IPv4 address. If set, this value will be set
+ in siaddr field.";
+ }
+ }
+
+ grouping server-hostname {
+ description "Server hostname grouping.";
+ leaf server-hostname {
+ type string;
+ description "Server hostname (up to 64 bytes).";
+ }
+ }
+
+ grouping boot-file-name {
+ description "Boot file name grouping.";
+ leaf boot-file-name {
+ type string;
+ description "Boot file name (up to 128 bytes).";
+ }
+ }
+
+ grouping authoritative {
+ description "Authoritative grouping.";
+ leaf authoritative {
+ type boolean;
+ description "NAK requests for unknown clients. If set to false
+ ignore them.";
+ }
+ }
+
+ grouping relay {
+ description "Relay grouping.";
+ leaf-list ip-addresses {
+ type inet:ipv4-address;
+ description "IPv4 addresses.";
+ }
+ }
+
+ grouping host {
+ description "Host grouping.";
+ leaf identifier-type {
+ type host-identifier-type;
+ mandatory true;
+ description "Host identifier type.";
+ }
+ uses dhcp:host-identifier;
+ leaf ip-address {
+ type inet:ipv4-address;
+ description "Host reserved IPv4 address.";
+ }
+ uses dhcp:host-hostname;
+ uses dhcp:host-client-classes;
+ uses option-data-list;
+ uses next-server;
+ uses server-hostname;
+ uses boot-file-name;
+ uses dhcp:host-user-context;
+ }
+
+ grouping reservations {
+ description "Host reservations grouping.";
+ list host {
+ key "identifier-type identifier";
+ description "Host reservation list.";
+ uses host;
+ }
+ }
+
+ grouping subnet4-list {
+ description "Subnet4 list grouping.";
+ list subnet4 {
+ key id;
+ ordered-by user;
+ description "List of IPv4 subnets.";
+ uses dhcp:valid-lifetime;
+ uses dhcp:min-valid-lifetime;
+ uses dhcp:max-valid-lifetime;
+ uses dhcp:renew-timer;
+ uses dhcp:rebind-timer;
+ uses dhcp:calculate-tee-times;
+ uses dhcp:t1-percent;
+ uses dhcp:t2-percent;
+ uses option-data-list;
+ list pool {
+ key "start-address end-address";
+ ordered-by user;
+ description "Pool list.";
+ leaf prefix {
+ type inet:ipv4-prefix;
+ description "Defines a pool of dynamic IPv4 addresses to be managed
+ by the server.";
+ }
+ leaf start-address {
+ type inet:ipv4-address;
+ mandatory true;
+ description "First IPv4 address in a pool.";
+ }
+ leaf end-address {
+ type inet:ipv4-address;
+ mandatory true;
+ description "Last IPv4 address in a pool.";
+ }
+ uses option-data-list;
+ uses dhcp:pool-client-class;
+ uses dhcp:pool-require-client-classes;
+ uses dhcp:pool-user-context;
+ }
+ leaf subnet {
+ type inet:ipv4-prefix;
+ mandatory true;
+ description "IPv4 subnet prefix.";
+ }
+ uses dhcp:subnet-interface;
+ uses dhcp:subnet-id;
+ uses dhcp:subnet-client-class;
+ uses dhcp:subnet-require-client-classes;
+ uses reservations {
+ refine host {
+ description "Subnet host reservations list.";
+ }
+ }
+ uses dhcp:reservation-mode;
+ container relay {
+ description "Optional information about relay agent.";
+ uses relay;
+ }
+ uses match-client-id;
+ uses next-server;
+ uses server-hostname;
+ uses boot-file-name;
+ leaf subnet-4o6-interface {
+ type string;
+ description "Subnet DHCPv4-over-DHCPv6 interface.";
+ }
+ leaf subnet-4o6-interface-id {
+ type string;
+ description "Subnet DHCPv4-over-DHCPv6 interface-id option.";
+ }
+ leaf subnet-4o6-subnet {
+ type inet:ipv6-prefix;
+ description "Subnet DHCPv4-over-DHCPv6 IPv6 prefix.";
+ }
+ uses authoritative;
+ uses dhcp:cache-max-age;
+ uses dhcp:cache-threshold;
+ uses dhcp:ddns-generated-prefix;
+ uses dhcp:ddns-override-client-update;
+ uses dhcp:ddns-override-no-update;
+ uses dhcp:ddns-qualifying-suffix;
+ uses dhcp:ddns-replace-client-name;
+ uses dhcp:ddns-send-updates;
+ uses dhcp:ddns-update-on-renew;
+ uses dhcp:ddns-use-conflict-resolution;
+ uses dhcp:hostname-char-replacement;
+ uses dhcp:hostname-char-set;
+ uses dhcp:reservations-global;
+ uses dhcp:reservations-in-subnet;
+ uses dhcp:reservations-out-of-pool;
+ uses dhcp:store-extended-info;
+ uses dhcp:subnet-user-context;
+ }
+ }
+
+ grouping client-classes {
+ description "Client class grouping.";
+ list client-class {
+ key name;
+ ordered-by user;
+ description "List of client classes.";
+ uses dhcp:class-name;
+ uses dhcp:class-test;
+ uses dhcp:class-only-if-required;
+ uses option-def-list;
+ uses option-data-list;
+ uses next-server;
+ uses server-hostname;
+ uses boot-file-name;
+ uses dhcp:valid-lifetime;
+ uses dhcp:min-valid-lifetime;
+ uses dhcp:max-valid-lifetime;
+ uses dhcp:class-user-context;
+ }
+ }
+
+ grouping option-def-list {
+ description "Option definition list grouping.";
+ list option-def {
+ key "code space";
+ description "Option definition entry.";
+ leaf code {
+ type uint8;
+ mandatory true;
+ description "Option code to be used by the new option definition.";
+ }
+ leaf space {
+ type string;
+ mandatory true;
+ description "Option space for the new option (typically dhcp4).";
+ }
+ uses dhcp:option-def-name;
+ uses dhcp:option-def-type;
+ uses dhcp:option-def-record-types;
+ uses dhcp:option-def-encapsulate;
+ uses dhcp:option-def-array;
+ uses dhcp:option-def-user-context;
+ }
+ }
+
+ grouping option-data-list {
+ description "Option data list grouping.";
+ list option-data {
+ key "code space";
+ description "Option data entry.";
+ leaf code {
+ type uint8;
+ mandatory true;
+ description "Option code.";
+ }
+ leaf space {
+ type string;
+ mandatory true;
+ description "Option space.";
+ }
+ uses dhcp:option-data-name;
+ uses dhcp:option-data-data;
+ uses dhcp:option-data-csv-format;
+ uses dhcp:option-data-always-send;
+ uses dhcp:option-data-user-context;
+ }
+ }
+
+ /*
+ * Data Nodes
+ */
+
+ container config {
+ // config true;
+ description "Contains DHCPv4 server configuration.";
+
+ uses dhcp:valid-lifetime;
+ uses dhcp:min-valid-lifetime;
+ uses dhcp:max-valid-lifetime;
+ uses dhcp:renew-timer;
+ uses dhcp:rebind-timer;
+ uses dhcp:calculate-tee-times;
+ uses dhcp:t1-percent;
+ uses dhcp:t2-percent;
+ uses dhcp:decline-probation-period;
+ uses subnet4-list;
+
+ list shared-network {
+ key name;
+ description "List of IPv4 shared networks.";
+ uses dhcp:network-name;
+ uses subnet4-list;
+ uses dhcp:network-interface;
+ uses dhcp:renew-timer;
+ uses dhcp:rebind-timer;
+ uses option-data-list;
+ uses match-client-id;
+ uses next-server;
+ uses server-hostname;
+ uses boot-file-name;
+ container relay {
+ description "Optional information about relay agent.";
+ uses relay;
+ }
+ uses authoritative;
+ uses dhcp:reservation-mode;
+ uses dhcp:network-client-class;
+ uses dhcp:network-require-client-classes;
+ uses dhcp:valid-lifetime;
+ uses dhcp:min-valid-lifetime;
+ uses dhcp:max-valid-lifetime;
+ uses dhcp:calculate-tee-times;
+ uses dhcp:t1-percent;
+ uses dhcp:t2-percent;
+ uses dhcp:cache-max-age;
+ uses dhcp:cache-threshold;
+ uses dhcp:ddns-generated-prefix;
+ uses dhcp:ddns-override-client-update;
+ uses dhcp:ddns-override-no-update;
+ uses dhcp:ddns-qualifying-suffix;
+ uses dhcp:ddns-replace-client-name;
+ uses dhcp:ddns-send-updates;
+ uses dhcp:ddns-update-on-renew;
+ uses dhcp:ddns-use-conflict-resolution;
+ uses dhcp:hostname-char-replacement;
+ uses dhcp:hostname-char-set;
+ uses dhcp:reservations-global;
+ uses dhcp:reservations-in-subnet;
+ uses dhcp:reservations-out-of-pool;
+ uses dhcp:store-extended-info;
+ uses dhcp:network-user-context;
+ }
+
+ container interfaces-config {
+ description "Network interfaces configuration.";
+ leaf-list interfaces {
+ type string;
+ description "Name of the interface (e.g. eth0) or name/address
+ (e.g. eth0/192.168.1.1) or * (use all interfaces).";
+ }
+ leaf dhcp-socket-type {
+ type enumeration {
+ enum "raw" {
+ description "DHCP service uses RAW sockets.";
+ }
+ enum "udp" {
+ description "DHCP service uses UDP sockets.";
+ }
+ }
+ description "Type of sockets to use.";
+ }
+ leaf outbound-interface {
+ type enumeration {
+ enum "same-as-inbound" {
+ description "Send the response on the interface where the query
+ was received.";
+ }
+ enum "use-routing" {
+ description "Use kernel routing.";
+ }
+ }
+ description "Control the interface used to send a response.";
+ }
+ uses dhcp:interfaces-re-detect;
+ uses dhcp:interfaces-service-sockets-require-all;
+ uses dhcp:interfaces-service-sockets-max-retries;
+ uses dhcp:interfaces-service-sockets-retry-wait-time;
+ uses dhcp:interfaces-user-context;
+ }
+
+ uses dhcp:databases;
+
+ leaf-list host-reservation-identifiers {
+ type host-identifier-type;
+ description "Host reservation identifiers.";
+ }
+
+ uses client-classes;
+ uses option-def-list;
+ uses option-data-list;
+ uses kea:hooks-libraries;
+ uses dhcp:expired-leases-processing;
+ uses dhcp:dhcp4o6-port;
+ uses kea:control-socket;
+ uses dhcp:hostname-char-set;
+ uses dhcp:hostname-char-replacement;
+ uses dhcp:dhcp-ddns;
+
+ leaf echo-client-id {
+ type boolean;
+ description "Send client-id back when the client sent it. This
+ is conformant with RFC6842, but some older, buggy
+ clients have problems with it.";
+ }
+
+ uses match-client-id;
+ uses next-server;
+ uses server-hostname;
+ uses boot-file-name;
+ uses authoritative;
+ uses kea:user-context {
+ refine user-context {
+ description "DHCPv4 server user context. Arbitrary JSON data can
+ be stored here.";
+ }
+ }
+ uses dhcp:sanity-checks;
+ uses dhcp:reservation-mode;
+ uses reservations {
+ refine host {
+ description "Global host reservations list.";
+ }
+ }
+ uses dhcp:config-control;
+ uses dhcp:server-tag;
+ uses dhcp:dhcp-queue-control;
+ uses kea:loggers;
+ uses dhcp:cache-max-age;
+ uses dhcp:cache-threshold;
+ uses dhcp:compatibility;
+ uses dhcp:ddns-generated-prefix;
+ uses dhcp:ddns-override-client-update;
+ uses dhcp:ddns-override-no-update;
+ uses dhcp:ddns-qualifying-suffix;
+ uses dhcp:ddns-replace-client-name;
+ uses dhcp:ddns-send-updates;
+ uses dhcp:ddns-update-on-renew;
+ uses dhcp:ddns-use-conflict-resolution;
+ uses dhcp:ip-reservations-unique;
+ uses dhcp:early-global-reservations-lookup;
+ uses dhcp:reservations-lookup-first;
+ uses dhcp:multi-threading;
+ uses dhcp:parked-packet-limit;
+ uses dhcp:reservations-global;
+ uses dhcp:reservations-in-subnet;
+ uses dhcp:reservations-out-of-pool;
+ uses dhcp:statistic-default-sample-age;
+ uses dhcp:statistic-default-sample-count;
+ uses dhcp:store-extended-info;
+ }
+
+ /*
+ * State data
+ */
+ container state {
+ config false;
+ description "State of Kea DHCPv4 server.";
+
+ container leases {
+ description "Kea DHCPv4 leases.";
+ list lease {
+ key ip-address;
+ description "List of Kea DHCPv4 leases.";
+ leaf ip-address {
+ type inet:ipv4-address;
+ mandatory true;
+ description "Lease IP address.";
+ }
+ leaf hw-address {
+ type binary;
+ mandatory true;
+ description "Lease hardware address.";
+ }
+ leaf client-id {
+ type binary;
+ description "Lease client-id.";
+ }
+ uses dhcp:valid-lifetime {
+ refine valid-lifetime {
+ mandatory true;
+ }
+ }
+ leaf cltt {
+ type uint32;
+ units "seconds";
+ mandatory true;
+ description "Lease client last transmission time.";
+ }
+ leaf subnet-id {
+ type uint32;
+ mandatory true;
+ description "Lease subnet ID.";
+ }
+ leaf fqdn-fwd {
+ type boolean;
+ description "Lease FQDN forward flag.";
+ }
+ leaf fqdn-rev {
+ type boolean;
+ description "Lease FQDN reverse lag.";
+ }
+ leaf hostname {
+ type string;
+ description "Lease hostname.";
+ }
+ leaf state {
+ type dhcp:lease-state;
+ description "Lease state.";
+ }
+ leaf user-context {
+ type kea:user-context;
+ description "Lease user context.";
+ }
+ }
+ }
+
+ container lease-stats {
+ description "Lease statistics.";
+ list subnet {
+ key subnet-id;
+ description "List of IPv4 subnets.";
+ leaf subnet-id {
+ type uint32;
+ mandatory true;
+ description "Subnet ID.";
+ }
+ leaf total-addresses {
+ type uint32;
+ mandatory true;
+ description "Total addresses counter.";
+ }
+ leaf assigned-addresses {
+ type uint32;
+ mandatory true;
+ description "Assigned addresses counter.";
+ }
+ leaf declined-addresses {
+ type uint32;
+ mandatory true;
+ description "Declined addresses counter.";
+ }
+ }
+ }
+
+ container hosts {
+ description "Kea DHCPv4 hosts.";
+ list host {
+ key "subnet-id identifier-type identifier";
+ description "List of Kea DHCPv4 hosts.";
+ uses host;
+ uses dhcp:host-subnet-id;
+ leaf auth-key {
+ type string;
+ description "Host authentication key (unused in DHCPv4).";
+ }
+ }
+ }
+ }
+}
diff --git a/src/share/yang/modules/kea-dhcp6-server@2022-07-27.yang b/src/share/yang/modules/kea-dhcp6-server@2022-07-27.yang
new file mode 100644
index 0000000..b7f6e7f
--- /dev/null
+++ b/src/share/yang/modules/kea-dhcp6-server@2022-07-27.yang
@@ -0,0 +1,715 @@
+module kea-dhcp6-server {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:kea-dhcp6-server";
+ prefix "kea-dhcp6-server";
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import kea-types {
+ prefix kea;
+ revision-date 2019-08-12;
+ }
+ import kea-dhcp-types {
+ prefix dhcp;
+ revision-date 2022-07-27;
+ }
+
+ organization "Internet Systems Consortium";
+ contact "kea-dev@lists.isc.org";
+ description "This model defines a YANG data model that can be
+ used to configure and manage a Kea DHCPv6 server.";
+
+ revision 2022-07-27 {
+ description "Added TLS parameters for database connections: trust-anchor,
+ cert-file, key-file, cipher-list. Added parameters which govern
+ the way the server behaves regarding detection of configured
+ interfaces: service-sockets-require-all,
+ service-sockets-max-retries, service-sockets-retry-wait-time.
+ Added parameters which govern reservations lookup:
+ early-global-reservations-lookup, reservations-lookup-first.";
+ }
+
+ revision 2021-11-24 {
+ description "Fixed store-extended-info. Added several containers and leaves:
+ compatibility, lenient-option-parsing, multi-threading,
+ enable-multi-threading, packet-queue-size, thread-pool-size,
+ valid-lifetime, min-valid-lifetime, max-valid-lifetime,
+ preferred-lifetime, min-preferred-lifetime,
+ max-preferred-lifetime, cache-max-age, cache-threshold,
+ ddns-generated-prefix, ddns-override-client-update,
+ ddns-override-no-update, ddns-qualifying-suffix,
+ ddns-replace-client-name, ddns-send-updates,
+ ddns-update-on-renew, ddns-use-conflict-resolution,
+ ip-reservations-unique, parked-packet-limit,
+ reservations-global, reservations-in-subnet,
+ reservations-out-of-pool, statistic-default-sample-age,
+ statistic-default-sample-count, store-extended-info, on-fail.";
+ }
+
+ revision 2021-08-25 {
+ description "Added store-extended-info";
+ }
+
+ revision 2019-08-12 {
+ description "Initial revision";
+ }
+
+ /*
+ * Typedefs
+ */
+ typedef host-identifier-type {
+ type enumeration {
+ enum "duid" {
+ description "DUID";
+ }
+ enum "hw-address" {
+ description "Hardware address";
+ }
+ enum "flex-id" {
+ description "Flexible identifier";
+ }
+ }
+ description "Host identifier type.";
+ }
+
+ /*
+ * Groupings
+ */
+ grouping data-directory {
+ description "Data directory grouping.";
+ leaf data-directory {
+ type string;
+ description "Server data directory.";
+ }
+ }
+
+ grouping preferred-lifetime {
+ description "Preferred lifetime grouping.";
+ leaf preferred-lifetime {
+ type uint32;
+ units "seconds";
+ description "Preferred lifetime.";
+ }
+ }
+
+ grouping min-preferred-lifetime {
+ description "Minimum preferred lifetime grouping.";
+ leaf min-preferred-lifetime {
+ type uint32;
+ units "seconds";
+ description "Minimum preferred lifetime.";
+ }
+ }
+
+ grouping max-preferred-lifetime {
+ description "Maximum preferred lifetime grouping.";
+ leaf max-preferred-lifetime {
+ type uint32;
+ units "seconds";
+ description "Maximum preferred lifetime.";
+ }
+ }
+
+ grouping interface-id {
+ description "Interface ID grouping.";
+ leaf interface-id {
+ type string;
+ description "Interface ID entry.";
+ }
+ }
+
+ grouping relay {
+ description "Relay grouping.";
+ leaf-list ip-addresses {
+ type inet:ipv6-address;
+ description "IPv6 addresses.";
+ }
+ }
+
+ grouping rapid-commit {
+ description "Rapid commit grouping.";
+ leaf rapid-commit {
+ type boolean;
+ description "Rapid commit entry.";
+ }
+ }
+
+ grouping host {
+ description "Host grouping.";
+ leaf identifier-type {
+ type host-identifier-type;
+ mandatory true;
+ description "Host identifier type.";
+ }
+ uses dhcp:host-identifier;
+ leaf-list ip-addresses {
+ type inet:ipv6-address;
+ description "Host reserved IP addresses.";
+ }
+ leaf-list prefixes {
+ type inet:ipv6-prefix;
+ description "Host reserved IP prefixes.";
+ }
+ uses dhcp:host-hostname;
+ uses dhcp:host-client-classes;
+ uses option-data-list;
+ uses dhcp:host-user-context;
+ }
+
+ grouping reservations {
+ description "Host reservations grouping.";
+ list host {
+ key "identifier-type identifier";
+ description "Host reservation list.";
+ uses host;
+ }
+ }
+
+ grouping subnet6-list {
+ description "Subnet6 list grouping.";
+ list subnet6 {
+ key id;
+ ordered-by user;
+ description "List of IPv6 subnets.";
+ uses preferred-lifetime;
+ uses min-preferred-lifetime;
+ uses max-preferred-lifetime;
+ uses dhcp:valid-lifetime;
+ uses dhcp:min-valid-lifetime;
+ uses dhcp:max-valid-lifetime;
+ uses dhcp:renew-timer;
+ uses dhcp:rebind-timer;
+ uses dhcp:calculate-tee-times;
+ uses dhcp:t1-percent;
+ uses dhcp:t2-percent;
+ uses option-data-list;
+ list pool {
+ key "start-address end-address";
+ ordered-by user;
+ description "Pool list.";
+ leaf prefix {
+ type inet:ipv6-prefix;
+ description "Pool prefix.";
+ }
+ leaf start-address {
+ type inet:ipv6-address;
+ mandatory true;
+ description "First IPv6 address in a pool.";
+ }
+ leaf end-address {
+ type inet:ipv6-address;
+ mandatory true;
+ description "Last IPv6 address in a pool.";
+ }
+ uses option-data-list;
+ uses dhcp:pool-client-class;
+ uses dhcp:pool-require-client-classes;
+ uses dhcp:pool-user-context;
+ }
+ list pd-pool {
+ key prefix;
+ ordered-by user;
+ description "Prefix delegation pool list.";
+ leaf prefix {
+ type inet:ipv6-prefix;
+ mandatory true;
+ description "IPv6 pool prefix.";
+ }
+ leaf delegated-len {
+ type uint8;
+ description "Prefix pool delegated length.";
+ }
+ uses option-data-list;
+ uses dhcp:client-class {
+ refine client-class {
+ description "Prefix pool client class guard. (only
+ clients belonging to this class will be
+ allowed in this pool).";
+ }
+ }
+ uses dhcp:require-client-classes {
+ refine require-client-classes {
+ description "Prefix pool require client classes.";
+ }
+ }
+ leaf excluded-prefix {
+ type inet:ipv6-prefix;
+ description "Prefix pool excluded prefix.";
+ }
+ uses kea:user-context {
+ refine user-context {
+ description "Prefix pool user context. Arbitrary JSON data
+ can be stored here.";
+ }
+ }
+ }
+ leaf subnet {
+ type inet:ipv6-prefix;
+ mandatory true;
+ description "IPv6 subnet prefix.";
+ }
+ uses dhcp:subnet-interface;
+ uses interface-id {
+ refine interface-id {
+ description "Subnet interface-id option.";
+ }
+ }
+ uses dhcp:subnet-id;
+ uses rapid-commit {
+ refine rapid-commit {
+ description "Subnet rapid commit flag.";
+ }
+ }
+ uses dhcp:subnet-client-class;
+ uses dhcp:require-client-classes;
+ uses reservations {
+ refine host {
+ description "Subnet host reservations list.";
+ }
+ }
+ uses dhcp:reservation-mode;
+ container relay {
+ description "Optional information about relay agent.";
+ uses relay;
+ }
+ uses dhcp:cache-max-age;
+ uses dhcp:cache-threshold;
+ uses dhcp:ddns-generated-prefix;
+ uses dhcp:ddns-override-client-update;
+ uses dhcp:ddns-override-no-update;
+ uses dhcp:ddns-qualifying-suffix;
+ uses dhcp:ddns-replace-client-name;
+ uses dhcp:ddns-send-updates;
+ uses dhcp:ddns-update-on-renew;
+ uses dhcp:ddns-use-conflict-resolution;
+ uses dhcp:hostname-char-replacement;
+ uses dhcp:hostname-char-set;
+ uses dhcp:reservations-global;
+ uses dhcp:reservations-in-subnet;
+ uses dhcp:reservations-out-of-pool;
+ uses dhcp:store-extended-info;
+ uses dhcp:subnet-user-context;
+ }
+ }
+
+ grouping client-classes {
+ description "Client class grouping.";
+ list client-class {
+ key name;
+ ordered-by user;
+ description "List of client classes.";
+ uses dhcp:class-name;
+ uses dhcp:class-test;
+ uses dhcp:class-only-if-required;
+ uses option-data-list;
+ uses preferred-lifetime;
+ uses min-preferred-lifetime;
+ uses max-preferred-lifetime;
+ uses dhcp:valid-lifetime;
+ uses dhcp:min-valid-lifetime;
+ uses dhcp:max-valid-lifetime;
+ uses dhcp:class-user-context;
+ }
+ }
+
+ grouping option-def-list {
+ description "Option definition list grouping.";
+ list option-def {
+ key "code space";
+ description "Option definition entry.";
+ leaf code {
+ type uint16;
+ mandatory true;
+ description "Option code to be used by the new option definition.";
+ }
+ leaf space {
+ type string;
+ mandatory true;
+ description "Option space for the new option (typically dhcp6).";
+ }
+ uses dhcp:option-def-name;
+ uses dhcp:option-def-type;
+ uses dhcp:option-def-record-types;
+ uses dhcp:option-def-encapsulate;
+ uses dhcp:option-def-array;
+ uses dhcp:option-def-user-context;
+ }
+ }
+
+ grouping option-data-list {
+ description "Option data list grouping.";
+ list option-data {
+ key "code space";
+ description "Option data entry.";
+ leaf code {
+ type uint16;
+ mandatory true;
+ description "Option code.";
+ }
+ leaf space {
+ type string;
+ mandatory true;
+ description "Option space.";
+ }
+ uses dhcp:option-data-name;
+ uses dhcp:option-data-data;
+ uses dhcp:option-data-csv-format;
+ uses dhcp:option-data-always-send;
+ uses dhcp:option-data-user-context;
+ }
+ }
+
+ /*
+ * Data Nodes
+ */
+
+ container config {
+ // config true;
+ description "Contains DHCPv6 server configuration.";
+
+ uses data-directory;
+ uses preferred-lifetime;
+ uses min-preferred-lifetime;
+ uses max-preferred-lifetime;
+ uses dhcp:valid-lifetime;
+ uses dhcp:min-valid-lifetime;
+ uses dhcp:max-valid-lifetime;
+ uses dhcp:renew-timer;
+ uses dhcp:rebind-timer;
+ uses dhcp:calculate-tee-times;
+ uses dhcp:t1-percent;
+ uses dhcp:t2-percent;
+ uses dhcp:decline-probation-period;
+ uses subnet6-list;
+
+ list shared-network {
+ key name;
+ uses dhcp:network-name;
+ description "List of IPv4 shared networks.";
+ uses subnet6-list;
+ uses dhcp:network-interface;
+ uses interface-id {
+ refine interface-id {
+ description "Shared network interface-id option.";
+ }
+ }
+ uses dhcp:renew-timer;
+ uses dhcp:rebind-timer;
+ uses option-data-list;
+ container relay {
+ description "Optional information about relay agent.";
+ uses relay;
+ }
+ uses dhcp:reservation-mode;
+ uses dhcp:network-client-class;
+ uses dhcp:require-client-classes;
+ uses preferred-lifetime;
+ uses min-preferred-lifetime;
+ uses max-preferred-lifetime;
+ uses rapid-commit {
+ refine rapid-commit {
+ description "Shared network rapid commit flag.";
+ }
+ }
+ uses dhcp:valid-lifetime;
+ uses dhcp:min-valid-lifetime;
+ uses dhcp:max-valid-lifetime;
+ uses dhcp:calculate-tee-times;
+ uses dhcp:t1-percent;
+ uses dhcp:t2-percent;
+ uses dhcp:cache-max-age;
+ uses dhcp:cache-threshold;
+ uses dhcp:ddns-generated-prefix;
+ uses dhcp:ddns-override-client-update;
+ uses dhcp:ddns-override-no-update;
+ uses dhcp:ddns-qualifying-suffix;
+ uses dhcp:ddns-replace-client-name;
+ uses dhcp:ddns-send-updates;
+ uses dhcp:ddns-update-on-renew;
+ uses dhcp:ddns-use-conflict-resolution;
+ uses dhcp:hostname-char-replacement;
+ uses dhcp:hostname-char-set;
+ uses dhcp:reservations-global;
+ uses dhcp:reservations-in-subnet;
+ uses dhcp:reservations-out-of-pool;
+ uses dhcp:store-extended-info;
+ uses dhcp:network-user-context;
+ }
+
+ container interfaces-config {
+ description "Network interfaces configuration.";
+ leaf-list interfaces {
+ type string;
+ description "Name of the interface (e.g. eth0) or name/address
+ (e.g. eth0/2001:db8::1) or * (use all interfaces).";
+ }
+ uses dhcp:interfaces-re-detect;
+ uses dhcp:interfaces-service-sockets-require-all;
+ uses dhcp:interfaces-service-sockets-max-retries;
+ uses dhcp:interfaces-service-sockets-retry-wait-time;
+ uses dhcp:interfaces-user-context;
+ }
+
+ uses dhcp:databases;
+
+ leaf-list relay-supplied-options {
+ type string;
+ description "Relay supplied options.";
+ }
+
+ leaf-list mac-sources {
+ type string;
+ description "MAC address sources.";
+ }
+
+ leaf-list host-reservation-identifiers {
+ type host-identifier-type;
+ description "Host reservation identifiers.";
+ }
+
+ uses client-classes;
+ uses option-def-list;
+ uses option-data-list;
+ uses kea:hooks-libraries;
+ uses dhcp:expired-leases-processing;
+
+ container server-id {
+ presence "Have server ID.";
+ description "Server DUID.";
+ leaf type {
+ type enumeration {
+ enum "LLT" {
+ description "Link-layer address and timestamp.";
+ }
+ enum "EN" {
+ description "Enterprise number.";
+ }
+ enum "LL" {
+ description "Link-layer address.";
+ }
+ }
+ description "Server DIOD type.";
+ }
+ leaf identifier {
+ type string;
+ description "Server DUID identifier.";
+ }
+ leaf time {
+ type uint32;
+ description "Server DUID time.";
+ }
+ leaf htype {
+ type uint16;
+ description "Server DUID hardware type.";
+ }
+ leaf enterprise-id {
+ type uint32;
+ description "Server DUID enterprise ID.";
+ }
+ leaf persist {
+ type boolean;
+ description "Server DUID persist flag.";
+ }
+ leaf user-context {
+ type kea:user-context;
+ description "Server DUID user context.";
+ }
+ }
+
+ uses dhcp:dhcp4o6-port;
+ uses kea:control-socket;
+ uses dhcp:hostname-char-set;
+ uses dhcp:hostname-char-replacement;
+ uses dhcp:dhcp-ddns;
+ uses kea:user-context {
+ refine user-context {
+ description "DHCPv6 server user context. Arbitrary JSON data can
+ be stored here.";
+ }
+ }
+ uses dhcp:sanity-checks;
+ uses dhcp:reservation-mode;
+ uses reservations {
+ refine host {
+ description "Global host reservations list.";
+ }
+ }
+ uses dhcp:config-control;
+ uses dhcp:server-tag;
+ uses dhcp:dhcp-queue-control;
+ uses kea:loggers;
+ uses dhcp:cache-max-age;
+ uses dhcp:cache-threshold;
+ uses dhcp:compatibility;
+ uses dhcp:ddns-generated-prefix;
+ uses dhcp:ddns-override-client-update;
+ uses dhcp:ddns-override-no-update;
+ uses dhcp:ddns-qualifying-suffix;
+ uses dhcp:ddns-replace-client-name;
+ uses dhcp:ddns-send-updates;
+ uses dhcp:ddns-update-on-renew;
+ uses dhcp:ddns-use-conflict-resolution;
+ uses dhcp:ip-reservations-unique;
+ uses dhcp:early-global-reservations-lookup;
+ uses dhcp:reservations-lookup-first;
+ uses dhcp:multi-threading;
+ uses dhcp:parked-packet-limit;
+ uses dhcp:reservations-global;
+ uses dhcp:reservations-in-subnet;
+ uses dhcp:reservations-out-of-pool;
+ uses dhcp:statistic-default-sample-age;
+ uses dhcp:statistic-default-sample-count;
+ uses dhcp:store-extended-info;
+ }
+
+ /*
+ * State data
+ */
+ container state {
+ config false;
+ description "State of Kea DHCPv6 server.";
+
+ container leases {
+ description "Kea DHCPv6 leases.";
+ list lease {
+ key ip-address;
+ description "List of Kea DHCPv6 leases.";
+ leaf ip-address {
+ type inet:ipv6-address;
+ mandatory true;
+ description "Lease IP address.";
+ }
+ leaf duid {
+ type binary;
+ mandatory true;
+ description "Lease DUID.";
+ }
+ uses dhcp:valid-lifetime {
+ refine valid-lifetime {
+ mandatory true;
+ }
+ }
+ leaf cltt {
+ type uint32;
+ units "seconds";
+ mandatory true;
+ description "Lease client last transmission time.";
+ }
+ leaf subnet-id {
+ type uint32;
+ mandatory true;
+ description "Lease subnet ID.";
+ }
+ leaf preferred-lifetime {
+ type uint32;
+ units "seconds";
+ mandatory true;
+ description "Lease preferred lifetime.";
+ }
+ leaf lease-type {
+ type enumeration {
+ enum "IA_NA" {
+ description "Identity association for non-temporary addresses.";
+ }
+ enum "IA_TA" {
+ description "Identity association for temporary addresses.";
+ }
+ enum "IA_PD" {
+ description "Identity association for prefix delegation.";
+ }
+ }
+ mandatory true;
+ description "Lease IA type.";
+ }
+ leaf iaid {
+ type uint32;
+ mandatory true;
+ description "Lease IA ID.";
+ }
+ leaf prefix-length {
+ type uint8 {
+ range 0..128;
+ }
+ description "Lease prefix length.";
+ }
+ leaf fqdn-fwd {
+ type boolean;
+ description "Lease FQDN forward flag.";
+ }
+ leaf fqdn-rev {
+ type boolean;
+ description "Lease FQDN reverse lag.";
+ }
+ leaf hostname {
+ type string;
+ description "Lease hostname.";
+ }
+ leaf state {
+ type dhcp:lease-state;
+ description "Lease state.";
+ }
+ leaf user-context {
+ type kea:user-context;
+ description "Lease user context.";
+ }
+ leaf hw-address {
+ type string;
+ description "Lease hardware address.";
+ }
+ }
+ }
+
+ container lease-stats {
+ description "Lease statistics.";
+ list subnet {
+ key subnet-id;
+ description "List of IPv6 subnets.";
+ leaf subnet-id {
+ type uint32;
+ mandatory true;
+ description "Subnet ID.";
+ }
+ leaf total-nas {
+ type uint32;
+ mandatory true;
+ description "Total non-temporary addresses counter.";
+ }
+ leaf assigned-nas {
+ type uint32;
+ mandatory true;
+ description "Assigned non-temporary counter.";
+ }
+ leaf declined-nas {
+ type uint32;
+ mandatory true;
+ description "Declined non-temporary addresses counter.";
+ }
+ leaf total-pds {
+ type uint32;
+ mandatory true;
+ description "Total delegated prefixes counter.";
+ }
+ leaf assigned-pds {
+ type uint32;
+ mandatory true;
+ description "Assigned delegated prefixe counter.";
+ }
+ }
+ }
+
+ container hosts {
+ description "Kea DHCPv6 hosts.";
+ list host {
+ key "subnet-id identifier-type identifier";
+ description "List of Kea DHCPv6 hosts.";
+ uses host;
+ uses dhcp:host-subnet-id;
+ leaf auth-key {
+ type string;
+ description "Host authentication key.";
+ }
+ }
+ }
+ }
+}
diff --git a/src/share/yang/modules/kea-types@2019-08-12.yang b/src/share/yang/modules/kea-types@2019-08-12.yang
new file mode 100644
index 0000000..a19c18f
--- /dev/null
+++ b/src/share/yang/modules/kea-types@2019-08-12.yang
@@ -0,0 +1,159 @@
+module kea-types {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:kea-types";
+ prefix "kea-types";
+
+ organization "Internet Systems Consortium";
+ contact "kea-dev@lists.isc.org";
+ description "This file defines some commonly used Kea types and groupings.";
+
+ revision 2019-08-12 {
+ description "Initial revision";
+ }
+
+ /*
+ * Typedef
+ */
+ typedef user-context {
+ type string;
+ description "User context (JSON map).";
+ }
+
+ /*
+ * Grouping
+ */
+ grouping user-context {
+ description "User context grouping.";
+ leaf user-context {
+ type user-context;
+ description "User context entry.";
+ }
+ }
+
+ grouping control-socket-info {
+ description "Control socket parameters.";
+ leaf socket-name {
+ type string;
+ mandatory true;
+ description "Path to the UNIX socket.";
+ }
+ leaf socket-type {
+ type enumeration {
+ enum "unix" {
+ description "Unix socket type.";
+ }
+ }
+ mandatory true;
+ description "Socket type.";
+ }
+ leaf user-context {
+ type user-context;
+ description "Control socket user context.";
+ }
+ }
+
+ grouping control-socket {
+ description "Control socket grouping.";
+ container control-socket {
+ presence "Have control socket.";
+ description "Control socket container.";
+ uses control-socket-info;
+ }
+ }
+
+ grouping hooks-libraries {
+ description "Hooks libraries grouping.";
+ list hook-library {
+ key library;
+ description "List of hook library.";
+ leaf library {
+ type string;
+ mandatory true;
+ description "Path to the DSO.";
+ }
+ leaf parameters {
+ type string;
+ description "Parameters (JSON value).";
+ }
+ }
+ }
+
+ grouping loggers {
+ description "Loggers.";
+ list logger {
+ key name;
+ description "List of loggers.";
+ leaf name {
+ type string;
+ mandatory true;
+ description "Name of the logger.";
+ }
+ list output-option {
+ key output;
+ description "List of output options.";
+ leaf output {
+ type string;
+ description "Type of output. Special values are stdout (standard
+ output), stderr (standard error), syslog (syslog using default
+ name), syslog:name (syslog using specified name). Any other
+ value is interpreted as a filename.";
+ }
+ leaf flush {
+ type boolean;
+ description "When true flush buffers after each message.";
+ }
+ leaf maxsize {
+ type uint32;
+ description "Maximum size of output file before rotation.
+ Values below 204800 including 0 disable rotation.";
+ }
+ leaf maxver {
+ type uint32 {
+ range 1..max;
+ }
+ description "Maximum version to keep a rotated output file.";
+ }
+ leaf pattern {
+ type string;
+ description "Custom log layout.";
+ }
+ }
+ leaf debuglevel {
+ type uint8 {
+ range 0..99;
+ }
+ description "What level of debug messages should be printed.";
+ }
+ leaf severity {
+ type enumeration {
+ enum "FATAL" {
+ description "Condition is so serious that the server cannot
+ continue executing";
+ }
+ enum "ERROR" {
+ description "Error condition. The server will continue
+ executing, but the results may not be as expected.";
+ }
+ enum "WARN" {
+ description "Out of the ordinary condition. However, the server
+ will continue executing normally.";
+ }
+ enum "INFO" {
+ description "Information message marking some event.";
+ }
+ enum "DEBUG" {
+ description "For debugging purposes.";
+ }
+ enum "NONE" {
+ description "All messages are inhibited.";
+ }
+ }
+ description "Category of messages logged.";
+ }
+ leaf user-context {
+ type user-context;
+ description "Logger user context.";
+ }
+ }
+ }
+}
diff --git a/src/share/yang/modules/keatest-module@2018-11-20.yang b/src/share/yang/modules/keatest-module@2018-11-20.yang
new file mode 100644
index 0000000..8993218
--- /dev/null
+++ b/src/share/yang/modules/keatest-module@2018-11-20.yang
@@ -0,0 +1,228 @@
+module keatest-module {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:keatest-module";
+ prefix tm;
+
+ organization "Sysrepo and ISC";
+ description
+ "ISC imported a test module from Sysrepo tests and adapted it
+ to kea testing regime.";
+ contact
+ "kea-dev@lists.isc.org";
+
+ revision 2018-11-20 {
+ description "Initial revision";
+ reference "";
+ }
+
+ container container {
+ config true;
+ list list {
+ leaf leaf {
+ type string;
+ }
+ leaf key1 {
+ type string;
+ }
+ leaf key2 {
+ type string;
+ }
+ key "key1 key2";
+ }
+ }
+
+ container main {
+ leaf enum {
+ type enumeration {
+ enum "yes" {
+ value 1;
+ }
+ enum "no" {
+ value 2;
+ }
+ enum "maybe" {
+ value 3;
+ }
+ }
+ }
+ leaf options {
+ type bits {
+ bit strict;
+ bit recursive;
+ bit logging;
+ }
+ }
+ leaf raw {
+ type binary;
+ }
+ leaf dec64 {
+ type decimal64{
+ fraction-digits 2;
+ }
+ }
+ leaf i8 {
+ type int8;
+ }
+ leaf i16 {
+ type int16;
+ }
+ leaf i32 {
+ type int32;
+ }
+ leaf i64 {
+ type int64;
+ }
+ leaf ui8 {
+ type uint8;
+ }
+ leaf ui16 {
+ type uint16;
+ }
+ leaf ui32 {
+ type uint32;
+ }
+ leaf ui64 {
+ type uint64;
+ }
+ leaf empty {
+ type empty;
+ }
+ leaf boolean {
+ type boolean;
+ }
+ leaf string {
+ type string;
+ }
+ leaf id_ref {
+ type identityref {
+ base base_id;
+ }
+ }
+ leaf-list numbers {
+ type uint8;
+ }
+ leaf instance_id {
+ type instance-identifier;
+ }
+ anyxml xml-data;
+ anydata any-data;
+ }
+
+ identity base_id;
+
+ identity id_1 {
+ base base_id;
+ }
+
+ identity id_2 {
+ base base_id;
+ }
+
+ list list {
+ key "key";
+ leaf key {
+ type string;
+ }
+ leaf id_ref {
+ type identityref {
+ base base_id;
+ }
+ }
+ leaf instance_id {
+ type instance-identifier;
+ }
+ leaf union {
+ type union {
+ type uint8;
+ type enumeration {
+ enum "infinity";
+ }
+ }
+ }
+ }
+
+ container kernel-modules {
+ list kernel-module {
+ key "name";
+
+ leaf name {
+ type string;
+ }
+
+ leaf location {
+ type string;
+ default "/lib/modules";
+ }
+
+ leaf loaded {
+ type boolean;
+ }
+
+ action load {
+ input {
+ leaf params {
+ mandatory "true";
+ type string;
+ }
+ leaf force {
+ type boolean;
+ default "false";
+ when "../../loaded = 'false'";
+ }
+ leaf dry-run {
+ type boolean;
+ default false;
+ }
+ }
+ output {
+ leaf return-code {
+ type uint8;
+ }
+ }
+ }
+ }
+ }
+
+ container presence-container {
+ presence "presence-container";
+
+ leaf topleaf1 {
+ type int8;
+ }
+
+ leaf topleaf2 {
+ type int8;
+ }
+
+ container child1 {
+ leaf child1-leaf {
+ type int8;
+ }
+
+ container grandchild1 {
+ leaf grandchild1-leaf {
+ type int8;
+ default 10;
+ }
+ }
+ }
+
+ container child2 {
+ leaf child2-leaf {
+ type int8;
+ }
+
+ container grandchild2 {
+ leaf grandchild2-leaf1 {
+ type int8;
+ }
+ leaf grandchild2-leaf2 {
+ type int8;
+ }
+ leaf grandchild2-leaf3 {
+ type int8;
+ }
+ }
+ }
+ }
+}
diff --git a/src/share/yang/modules/utils/Makefile.am b/src/share/yang/modules/utils/Makefile.am
new file mode 100644
index 0000000..277f93d
--- /dev/null
+++ b/src/share/yang/modules/utils/Makefile.am
@@ -0,0 +1,27 @@
+SUBDIRS = .
+
+yangdir = ${datarootdir}/${PACKAGE_NAME}/yang/modules/utils
+
+yang_list =
+yang_list += check-hashes.sh
+yang_list += check-revisions.sh
+
+EXTRA_DIST = ${yang_list}
+
+if HAVE_SYSREPO
+
+yang_SCRIPTS = reinstall.sh
+
+# Shell tests
+SHTESTS =
+SHTESTS += check-hashes.sh
+SHTESTS += check-revisions.sh
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+TESTS = $(SHTESTS)
+
+# Don't install tests.
+noinst_SCRIPTS = $(SHTESTS)
+
+endif
diff --git a/src/share/yang/modules/utils/Makefile.in b/src/share/yang/modules/utils/Makefile.in
new file mode 100644
index 0000000..00edbb2
--- /dev/null
+++ b/src/share/yang/modules/utils/Makefile.in
@@ -0,0 +1,906 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/share/yang/modules/utils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = reinstall.sh
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(yangdir)"
+SCRIPTS = $(noinst_SCRIPTS) $(yang_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/reinstall.sh.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+yangdir = ${datarootdir}/${PACKAGE_NAME}/yang/modules/utils
+yang_list = check-hashes.sh check-revisions.sh
+EXTRA_DIST = ${yang_list}
+@HAVE_SYSREPO_TRUE@yang_SCRIPTS = reinstall.sh
+
+# Shell tests
+@HAVE_SYSREPO_TRUE@SHTESTS = check-hashes.sh check-revisions.sh
+
+# Run shell tests on "make check".
+@HAVE_SYSREPO_TRUE@check_SCRIPTS = $(SHTESTS)
+@HAVE_SYSREPO_TRUE@TESTS = $(SHTESTS)
+
+# Don't install tests.
+@HAVE_SYSREPO_TRUE@noinst_SCRIPTS = $(SHTESTS)
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/share/yang/modules/utils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/share/yang/modules/utils/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+reinstall.sh: $(top_builddir)/config.status $(srcdir)/reinstall.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+install-yangSCRIPTS: $(yang_SCRIPTS)
+ @$(NORMAL_INSTALL)
+ @list='$(yang_SCRIPTS)'; test -n "$(yangdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(yangdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(yangdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n' \
+ -e 'h;s|.*|.|' \
+ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+ if (++n[d] == $(am__install_max)) { \
+ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+ else { print "f", d "/" $$4, $$1 } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(yangdir)$$dir'"; \
+ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(yangdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-yangSCRIPTS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(yang_SCRIPTS)'; test -n "$(yangdir)" || exit 0; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 's,.*/,,;$(transform)'`; \
+ dir='$(DESTDIR)$(yangdir)'; $(am__uninstall_files_from_dir)
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_SCRIPTS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(SCRIPTS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(yangdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-yangSCRIPTS
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-yangSCRIPTS
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-TESTS check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags ctags-am distclean distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip install-yangSCRIPTS \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-yangSCRIPTS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/share/yang/modules/utils/check-hashes.sh b/src/share/yang/modules/utils/check-hashes.sh
new file mode 100755
index 0000000..968289d
--- /dev/null
+++ b/src/share/yang/modules/utils/check-hashes.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# Check hashes
+#
+# This developer script verifies recorded hashes still match the
+# result of SHA-256 checksums of the YIN format.
+# Requires yanglint to translate YANG to YIN formats and openssl
+# for a system independent SHA-256.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Change directory to the YANG modules' directory.
+script_path=$(cd "$(dirname "${0}")" && pwd)
+cd "${script_path}/.."
+
+amend=false
+if test "${1-}" = '-a' || test "${1-}" = '--amend'; then
+ amend=true
+fi
+
+exit_code=0
+for m in *.yang
+do
+ hash1=$(yanglint -f yin "${m}" | openssl dgst -sha256 | sed 's/(stdin)= //' | sed 's/SHA2-256//')
+ h="hashes/$(basename "${m}" .yang).hash"
+ if test -f "${h}"
+ then
+ hash2=$(cat "${h}")
+ if test "$hash1" != "$hash2"
+ then
+ exit_code=$((exit_code | 1))
+ printf 'hash mismatch on %s expected %s in %s\n' "${m}" "${hash1}" "${h}"
+ if "${amend}"; then
+ printf '%s\n' "${hash1}" > "${h}"
+ fi
+ fi
+ else
+ exit_code=$((exit_code | 2))
+ printf 'missing hash file %s for %s\n' "${h}" "${m}"
+ if "${amend}"; then
+ printf '%s\n' "${hash1}" > "${h}"
+ fi
+ fi
+done
+
+exit "${exit_code}"
diff --git a/src/share/yang/modules/utils/check-revisions.sh b/src/share/yang/modules/utils/check-revisions.sh
new file mode 100755
index 0000000..04c9615
--- /dev/null
+++ b/src/share/yang/modules/utils/check-revisions.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# Check revisions
+#
+# This developer script verifies versions in module contents match
+# the version in the name.
+# Requires yanglint to translate YANG to YIN formats.
+# Fixme: use xlstproc to extract the revision.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Change directory to the YANG modules' directory.
+script_path=$(cd "$(dirname "${0}")" && pwd)
+cd "${script_path}/.."
+
+error=0
+for m in *.yang
+do
+ rev1=$(yanglint -f yin "${m}" | grep '<revision date=' | head -1 | sed \
+ 's/.*<revision date="\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\)".*/\1/')
+ rev2=$(echo "${m}" | sed \
+ 's/.*@\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\)\..*/\1/')
+
+ if test "${rev1}" != "${rev2}"
+ then
+ error=1
+ printf 'revision mismatch on %s got %s\n' "${m}" "${rev1}"
+ fi
+done
+exit $error
diff --git a/src/share/yang/modules/utils/reinstall.sh.in b/src/share/yang/modules/utils/reinstall.sh.in
new file mode 100644
index 0000000..5924a6c
--- /dev/null
+++ b/src/share/yang/modules/utils/reinstall.sh.in
@@ -0,0 +1,169 @@
+#!/bin/sh
+
+# Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form 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/.
+
+# Usage:
+#
+# reinstall.sh [-d|--debug] [-h|--help] [-s|--sysrepo ${SYSREPO_INSTALLATION}]
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Print usage.
+# Expressions don't expand in single quotes, use double quotes for that. [SC2016]
+# shellcheck disable=SC2016
+print_usage() {
+ printf \
+'Usage: %s {{options}}
+Options:
+ [-d|--debug] enable debug mode, showing every executed command
+ [-h|--help] print usage (this text)
+ [-s|--sysrepo ${SYSREPO_INSTALLATION}] point to sysrepo installation which is needed for sysrepoctl
+ [-u|--uninstall-first] uninstall before installing
+' \
+ "$(basename "${0}")"
+}
+
+# Define some ANSI color codes.
+if test -t 1; then
+ red='\033[91m'
+ reset='\033[0m'
+else
+ red=
+ reset=
+fi
+
+# Parse parameters.
+while test ${#} -gt 0; do
+ case "${1}" in
+ # [-d|--debug] enable debug mode, showing every executed command
+ '-d'|'--debug') set -vx ;;
+
+ # [-h|--help] print usage (this text)
+ '-h'|'--help') print_usage; exit 0 ;;
+
+ # [-s|--sysrepo ${SYSREPO_INSTALLATION}] point to sysrepo installation which is needed for sysrepoctl
+ '-s'|'--sysrepo') shift; sysrepo=${1} ;;
+
+ # [-u|--uninstall-first] uninstall before installing
+ '-u'|'--uninstall-first') uninstall_first=true ;;
+
+ # Unrecognized argument
+ *)
+ printf "${red}ERROR: Unrecognized argument '%s'${reset}\\n" "${1}" 1>&2; print_usage; exit 1 ;;
+ esac; shift
+done
+
+# Default arguments
+test -z "${sysrepo+x}" && sysrepo='/usr/local'
+test -z "${uninstall_first+x}" && uninstall_first='false'
+
+#------------------------------------------------------------------------------#
+
+# Get script path.
+script_path=$(cd "$(dirname "${0}")" && pwd)
+
+# shellcheck disable=SC2034
+# prefix appears unused. Verify use (or export if used externally).
+# reason: prefix is used in datarootdir (@datarootdir@) below.
+prefix="@prefix@"
+
+# Find modules location.
+# If script is in sources, use modules from sources.
+# If script is in installation, use modules from installation.
+for i in \
+ "@datarootdir@/@PACKAGE_NAME@/yang/modules" \
+ "@abs_top_builddir@/src/share/yang/modules" \
+; do
+ if test "${script_path}" = "${i}/utils"; then
+ modules="${i}"
+ break
+ fi
+done
+if test -z "${modules+x}"; then
+ printf 'ERROR: cannot find location of modules. Use this script from sources or from installation.' >&2
+ exit 1
+fi
+
+# sysrepoctl does not set its rpath. Set LD_LIBRARY_PATH instead.
+export LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}:${sysrepo}/lib:${sysrepo}/lib64"
+
+# Check if module is installed.
+is_module_installed() {
+ module=${1}
+ if test "$("${sysrepo}/bin/sysrepoctl" -l | grep -F '| I' | cut -d ' ' -f 1 | tail -n +7 | head -n -1 | grep -Ec "^${module}")" -eq 0; then
+ # not installed
+ return 1
+ fi
+ # installed
+ return 0
+}
+
+# Install a module from the Kea sources. Should upgrade automatically to a newer
+# revision.
+install_kea_module() {
+ module=${1}
+
+ if is_module_installed "${module}"; then
+ # Upgrade.
+ flag="-U"
+ else
+ # Install.
+ flag="-i"
+ fi
+
+ # Find a module starting with given name and act on it.
+ find "${modules}" -maxdepth 1 -type f -name "${module}*.yang" -exec \
+ "${sysrepo}/bin/sysrepoctl" "${flag}" {} -s "${modules}" -v 4 \;
+}
+
+# Uninstall a module if installed.
+uninstall_module() {
+ module=${1}
+ if ! is_module_installed "${module}"; then
+ return;
+ fi
+ "${sysrepo}/bin/sysrepoctl" -u "${module}" -v 4
+}
+
+# Install all YANG modules in dependency order.
+install_yang_modules() {
+ install_kea_module 'keatest-module'
+ install_kea_module 'ietf-interfaces'
+ install_kea_module 'ietf-dhcpv6-common'
+ install_kea_module 'ietf-dhcpv6-client'
+ install_kea_module 'ietf-dhcpv6-relay'
+ install_kea_module 'ietf-dhcpv6-server'
+ install_kea_module 'ietf-yang-types'
+ install_kea_module 'ietf-dhcpv6-options'
+ install_kea_module 'ietf-dhcpv6-types'
+ install_kea_module 'ietf-inet-types'
+ install_kea_module 'kea-types'
+ install_kea_module 'kea-dhcp-types'
+ install_kea_module 'kea-dhcp-ddns'
+ install_kea_module 'kea-ctrl-agent'
+ install_kea_module 'kea-dhcp4-server'
+ install_kea_module 'kea-dhcp6-server'
+}
+
+# Uninstall all YANG modules in reverse dependency order. Only uninstalls Kea
+# modules. IETF modules might be dependencies to sysrepo internal modules.
+uninstall_yang_modules() {
+ uninstall_module 'kea-dhcp6-server'
+ uninstall_module 'kea-dhcp4-server'
+ uninstall_module 'kea-ctrl-agent'
+ uninstall_module 'kea-dhcp-ddns'
+ uninstall_module 'kea-dhcp-types'
+ uninstall_module 'kea-types'
+ uninstall_module 'keatest-module'
+}
+
+if "${uninstall_first}"; then
+ uninstall_yang_modules
+fi
+install_yang_modules