vyos@R3:/opt/vyatta/etc/config-migrate/migrate/dhcp-server$ cat 4-to-5
#!/usr/bin/env python3

# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library.  If not, see <http://www.gnu.org/licenses/>.

# Removes boolean operator from:
#   - "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 ip-forwarding enable (true|false)"
#   - "set service dhcp-server shared-network-name <xyz> authoritative (true|false)"
#   - "set service dhcp-server disabled (true|false)"

from vyos.configtree import ConfigTree

def migrate(config: ConfigTree) -> None:
    if not config.exists(['service', 'dhcp-server']):
        # Nothing to do
        return
    else:
        base = ['service', 'dhcp-server']
        # Make node "set service dhcp-server dynamic-dns-update enable (true|false)" valueless
        if config.exists(base + ['dynamic-dns-update']):
            bool_val = config.return_value(base + ['dynamic-dns-update', 'enable'])

            # Delete the node with the old syntax
            config.delete(base + ['dynamic-dns-update'])
            if str(bool_val) == 'true':
                # Enable dynamic-dns-update with new syntax
                config.set(base + ['dynamic-dns-update'], value=None)

        # Make node "set service dhcp-server disabled (true|false)" valueless
        if config.exists(base + ['disabled']):
            bool_val = config.return_value(base + ['disabled'])

            # Delete the node with the old syntax
            config.delete(base + ['disabled'])
            if str(bool_val) == 'true':
                # Now disable DHCP server with the new syntax
                config.set(base + ['disable'], value=None)

        # Make node "set service dhcp-server hostfile-update (enable|disable) valueless
        if config.exists(base + ['hostfile-update']):
            bool_val = config.return_value(base + ['hostfile-update'])

            # Delete the node with the old syntax incl. all subnodes
            config.delete(base + ['hostfile-update'])
            if str(bool_val) == 'enable':
                # Enable hostfile update with new syntax
                config.set(base + ['hostfile-update'], value=None)

        # Run this for every instance if 'shared-network-name'
        for network in config.list_nodes(base + ['shared-network-name']):
            base_network = base + ['shared-network-name', network]
            # format as tag node to avoid loading problems
            config.set_tag(base + ['shared-network-name'])

            # Run this for every specified 'subnet'
            for subnet in config.list_nodes(base_network + ['subnet']):
                base_subnet = base_network + ['subnet', subnet]
                # format as tag node to avoid loading problems
                config.set_tag(base_network + ['subnet'])

                # Make node "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 ip-forwarding enable" valueless
                if config.exists(base_subnet + ['ip-forwarding', 'enable']):
                    bool_val = config.return_value(base_subnet + ['ip-forwarding', 'enable'])
                    # Delete the node with the old syntax
                    config.delete(base_subnet + ['ip-forwarding'])
                    if str(bool_val) == 'true':
                        # Recreate node with new syntax
                        config.set(base_subnet + ['ip-forwarding'], value=None)

                # Rename node "set service dhcp-server shared-network-name <xyz> subnet 172.31.0.0/24 start <172.16.0.4> stop <172.16.0.9>
                if config.exists(base_subnet + ['start']):
                    # This is the new "range" id for DHCP lease ranges
                    r_id = 0
                    for range in config.list_nodes(base_subnet + ['start']):
                        range_start = range
                        range_stop = config.return_value(base_subnet + ['start', range_start, 'stop'])

                        # Delete the node with the old syntax
                        config.delete(base_subnet + ['start', range_start])

                        # Create the node for the new syntax
                        # Note: range is a tag node, counter is its child, not a value
                        config.set(base_subnet + ['range', r_id])
                        config.set(base_subnet + ['range', r_id, 'start'], value=range_start)
                        config.set(base_subnet + ['range', r_id, 'stop'], value=range_stop)

                        # format as tag node to avoid loading problems
                        config.set_tag(base_subnet + ['range'])

                        # increment range id for possible next range definition
                        r_id += 1

                    # Delete the node with the old syntax
                    config.delete(['service', 'dhcp-server', 'shared-network-name', network, 'subnet', subnet, 'start'])


            # Make node "set service dhcp-server shared-network-name <xyz> authoritative" valueless
            if config.exists(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative']):
                authoritative = config.return_value(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative'])

                # Delete the node with the old syntax
                config.delete(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative'])

                # Recreate node with new syntax - if required
                if authoritative == "enable":
                    config.set(['service', 'dhcp-server', 'shared-network-name', network, 'authoritative'])
vyos@R3:/opt/vyatta/etc/config-migrate/migrate/dhcp-server$ cat 5-to-6
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library.  If not, see <http://www.gnu.org/licenses/>.

# T1968: allow multiple static-routes to be configured
# T3838: rename dns-server -> name-server

from vyos.configtree import ConfigTree

base = ['service', 'dhcp-server']

def migrate(config: ConfigTree) -> None:
    if not config.exists(base + ['shared-network-name']):
        # Nothing to do
        return

    # Run this for every instance if 'shared-network-name'
    for network in config.list_nodes(base + ['shared-network-name']):
        base_network = base + ['shared-network-name', network]

        if not config.exists(base_network + ['subnet']):
            continue

        # Run this for every specified 'subnet'
        for subnet in config.list_nodes(base_network + ['subnet']):
            base_subnet = base_network + ['subnet', subnet]

            # T1968: allow multiple static-routes to be configured
            if config.exists(base_subnet + ['static-route']):
                prefix = config.return_value(base_subnet + ['static-route', 'destination-subnet'])
                router = config.return_value(base_subnet + ['static-route', 'router'])
                config.delete(base_subnet + ['static-route'])

                config.set(base_subnet + ['static-route', prefix, 'next-hop'], value=router)
                config.set_tag(base_subnet + ['static-route'])

            # T3838: rename dns-server -> name-server
            if config.exists(base_subnet + ['dns-server']):
                config.rename(base_subnet + ['dns-server'], 'name-server')


            # T3672: ISC DHCP server only supports one failover peer
            if config.exists(base_subnet + ['failover']):
                # There can only be one failover configuration, if none is present
                # we add the first one
                if not config.exists(base + ['failover']):
                    local = config.return_value(base_subnet + ['failover', 'local-address'])
                    remote = config.return_value(base_subnet + ['failover', 'peer-address'])
                    status = config.return_value(base_subnet + ['failover', 'status'])
                    name = config.return_value(base_subnet + ['failover', 'name'])

                    config.set(base + ['failover', 'remote'], value=remote)
                    config.set(base + ['failover', 'source-address'], value=local)
                    config.set(base + ['failover', 'status'], value=status)
                    config.set(base + ['failover', 'name'], value=name)

                config.delete(base_subnet + ['failover'])
                config.set(base_subnet + ['enable-failover'])
vyos@R3:/opt/vyatta/etc/config-migrate/migrate/dhcp-server$ cat 6-to-7
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library.  If not, see <http://www.gnu.org/licenses/>.

# T6079: Disable duplicate static mappings

from vyos.configtree import ConfigTree

base = ['service', 'dhcp-server']

def migrate(config: ConfigTree) -> None:
    if not config.exists(base + ['shared-network-name']):
        # Nothing to do
        return

    # Run this for every instance if 'shared-network-name'
    for network in config.list_nodes(base + ['shared-network-name']):
        base_network = base + ['shared-network-name', network]

        if not config.exists(base_network + ['subnet']):
            continue

        for subnet in config.list_nodes(base_network + ['subnet']):
            base_subnet = base_network + ['subnet', subnet]

            if config.exists(base_subnet + ['static-mapping']):
                used_mac = []
                used_ip = []

                for mapping in config.list_nodes(base_subnet + ['static-mapping']):
                    base_mapping = base_subnet + ['static-mapping', mapping]

                    if config.exists(base_mapping + ['mac-address']):
                        mac = config.return_value(base_mapping + ['mac-address'])

                        if mac in used_mac:
                            config.set(base_mapping + ['disable'])
                        else:
                            used_mac.append(mac)

                    if config.exists(base_mapping + ['ip-address']):
                        ip = config.return_value(base_mapping + ['ip-address'])

                        if ip in used_ip:
                            config.set(base_subnet + ['static-mapping', mapping, 'disable'])
                        else:
                            used_ip.append(ip)
vyos@R3:/opt/vyatta/etc/config-migrate/migrate/dhcp-server$ cat 7-to-8
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library.  If not, see <http://www.gnu.org/licenses/>.

# T3316: Migrate to Kea
#        - global-parameters will not function
#        - shared-network-parameters will not function
#        - subnet-parameters will not function
#        - static-mapping-parameters will not function
#        - host-decl-name is on by default, option removed
#        - ping-check no longer supported
#        - failover is default enabled on all subnets that exist on failover servers

from vyos.configtree import ConfigTree

base = ['service', 'dhcp-server']

def migrate(config: ConfigTree) -> None:
    if not config.exists(base):
        # Nothing to do
        return

    if config.exists(base + ['host-decl-name']):
        config.delete(base + ['host-decl-name'])

    if config.exists(base + ['global-parameters']):
        config.delete(base + ['global-parameters'])

    if config.exists(base + ['shared-network-name']):
        for network in config.list_nodes(base + ['shared-network-name']):
            base_network = base + ['shared-network-name', network]

            if config.exists(base_network + ['shared-network-parameters']):
                config.delete(base_network +['shared-network-parameters'])

            if not config.exists(base_network + ['subnet']):
                continue

            # Run this for every specified 'subnet'
            for subnet in config.list_nodes(base_network + ['subnet']):
                base_subnet = base_network + ['subnet', subnet]

                if config.exists(base_subnet + ['enable-failover']):
                    config.delete(base_subnet + ['enable-failover'])

                if config.exists(base_subnet + ['subnet-parameters']):
                    config.delete(base_subnet + ['subnet-parameters'])

                if config.exists(base_subnet + ['static-mapping']):
                    for mapping in config.list_nodes(base_subnet + ['static-mapping']):
                        if config.exists(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']):
                            config.delete(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters'])
vyos@R3:/opt/vyatta/etc/config-migrate/migrate/dhcp-server$ cat 8-to-9
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library.  If not, see <http://www.gnu.org/licenses/>.

# T3316:
# - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore)
# - Rename "service dhcp-server shared-network-name ... static-mapping <hostname> mac-address ..."
#       to "service dhcp-server shared-network-name ... static-mapping <hostname> mac ..."

import re
from vyos.configtree import ConfigTree

base = ['service', 'dhcp-server', 'shared-network-name']

def migrate(config: ConfigTree) -> None:
    if not config.exists(base):
        # Nothing to do
        return

    for network in config.list_nodes(base):
        # Run this for every specified 'subnet'
        if config.exists(base + [network, 'subnet']):
            for subnet in config.list_nodes(base + [network, 'subnet']):
                base_subnet = base + [network, 'subnet', subnet]
                if config.exists(base_subnet + ['static-mapping']):
                    for hostname in config.list_nodes(base_subnet + ['static-mapping']):
                        base_mapping = base_subnet + ['static-mapping', hostname]

                        # Rename the 'mac-address' node to 'mac'
                        if config.exists(base_mapping + ['mac-address']):
                            config.rename(base_mapping + ['mac-address'], 'mac')

                        # Adjust hostname to have valid FQDN characters only
                        new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname)
                        if new_hostname != hostname:
                            config.rename(base_mapping, new_hostname)
vyos@R3:/opt/vyatta/etc/config-migrate/migrate/dhcp-server$ cat 9-to-10
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library.  If not, see <http://www.gnu.org/licenses/>.

# T3316:
# - Migrate dhcp options under new option node
# - Add subnet IDs to existing subnets

from vyos.configtree import ConfigTree

base = ['service', 'dhcp-server', 'shared-network-name']

option_nodes = ['bootfile-name', 'bootfile-server', 'bootfile-size', 'captive-portal',
                'client-prefix-length', 'default-router', 'domain-name', 'domain-search',
                'name-server', 'ip-forwarding', 'ipv6-only-preferred', 'ntp-server',
                'pop-server', 'server-identifier', 'smtp-server', 'static-route',
                'tftp-server-name', 'time-offset', 'time-server', 'time-zone',
                'vendor-option', 'wins-server', 'wpad-url']


def migrate(config: ConfigTree) -> None:
    if not config.exists(base):
        # Nothing to do
        return

    subnet_id = 1

    for network in config.list_nodes(base):
        for option in option_nodes:
            if config.exists(base + [network, option]):
                config.set(base + [network, 'option'])
                config.copy(base + [network, option], base + [network, 'option', option])
                config.delete(base + [network, option])

        if config.exists(base + [network, 'subnet']):
            for subnet in config.list_nodes(base + [network, 'subnet']):
                base_subnet = base + [network, 'subnet', subnet]

                for option in option_nodes:
                    if config.exists(base_subnet + [option]):
                        config.set(base_subnet + ['option'])
                        config.copy(base_subnet + [option], base_subnet + ['option', option])
                        config.delete(base_subnet + [option])

                config.set(base_subnet + ['subnet-id'], value=subnet_id)
                subnet_id += 1
vyos@R3:/opt/vyatta/etc/config-migrate/migrate/dhcp-server$ cat 10-to-11
# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library.  If not, see <http://www.gnu.org/licenses/>.

# T6171: rename "service dhcp-server failover" to "service dhcp-server high-availability"

from vyos.configtree import ConfigTree

base = ['service', 'dhcp-server']

def migrate(config: ConfigTree) -> None:
    if not config.exists(base):
        # Nothing to do
        return

    if config.exists(base + ['failover']):
        config.rename(base + ['failover'],'high-availability')
