summaryrefslogtreecommitdiff
path: root/src/migration-scripts/vrf/2-to-3
blob: 8e0f97141d2780f481b7bc185e87dc5b516494a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env python3
#
# Copyright (C) 2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Since connection tracking zones are int16, VRFs tables maximum value must
# be limited to 65535
# Also, interface names in nftables cannot start from numbers,
# so VRF name should not start from a number

from sys import argv
from sys import exit
from random import randrange
from random import choice
from string import ascii_lowercase
from vyos.configtree import ConfigTree
import re


# Helper function to find all config items with a VRF name
def _search_vrfs(config_commands, vrf_name):
    vrf_values = []
    # Regex to find path of config command with old VRF
    regex_filter = re.compile(rf'^set (?P<cmd_path>[^\']+vrf) \'{vrf_name}\'$')
    # Check each command for VRF value
    for config_command in config_commands:
        search_result = regex_filter.search(config_command)
        if search_result:
            # Append VRF command to a list
            vrf_values.append(search_result.group('cmd_path').split())
    if vrf_values:
        return vrf_values
    else:
        return None


# Helper function to find all config items with a table number
def _search_tables(config_commands, table_num):
    table_items = {'table_tags': [], 'table_values': []}
    # Regex to find values and nodes with a table number
    regex_tags = re.compile(rf'^set (?P<cmd_path>[^\']+table {table_num}) ?.*$')
    regex_values = re.compile(
        rf'^set (?P<cmd_path>[^\']+table) \'{table_num}\'$')
    for config_command in config_commands:
        # Search for tag nodes
        search_result = regex_tags.search(config_command)
        if search_result:
            # Append table node path to a tag nodes list
            cmd_path = search_result.group('cmd_path').split()
            if cmd_path not in table_items['table_tags']:
                table_items['table_tags'].append(cmd_path)
        # Search for value nodes
        search_result = regex_values.search(config_command)
        if search_result:
            # Append table node path to a value nodes list
            table_items['table_values'].append(
                search_result.group('cmd_path').split())
    return table_items


if (len(argv) < 2):
    print("Must specify file name!")
    exit(1)

file_name = argv[1]

with open(file_name, 'r') as f:
    config_file = f.read()

base = ['vrf', 'name']
config = ConfigTree(config_file)

if not config.exists(base):
    # Nothing to do
    exit(0)

# Get a list of all currently used VRFs and tables
vrfs_current = {}
for vrf in config.list_nodes(base):
    vrfs_current[vrf] = int(config.return_value(base + [vrf, 'table']))

# Check VRF names and table numbers
name_regex = re.compile(r'^\d.*$')
for vrf_name, vrf_table in vrfs_current.items():
    # Check table number
    if vrf_table > 65535:
        # Find new unused table number
        vrfs_current[vrf_name] = None
        while not vrfs_current[vrf_name]:
            table_random = randrange(100, 65535)
            if table_random not in vrfs_current.values():
                vrfs_current[vrf_name] = table_random
        # Update number to a new one
        config.set(['vrf', 'name', vrf_name, 'table'],
                   vrfs_current[vrf_name],
                   replace=True)
        # Check config items with old table number and replace to new one
        config_commands = config.to_commands().split('\n')
        table_config_lines = _search_tables(config_commands, vrf_table)
        # Rename table nodes
        if table_config_lines.get('table_tags'):
            for table_config_path in table_config_lines.get('table_tags'):
                config.rename(table_config_path, f'{vrfs_current[vrf_name]}')
        # Replace table values
        if table_config_lines.get('table_values'):
            for table_config_path in table_config_lines.get('table_values'):
                config.set(table_config_path,
                           f'{vrfs_current[vrf_name]}',
                           replace=True)

    # Check VRF name
    if name_regex.match(vrf_name):
        vrf_name_new = None
        while not vrf_name_new:
            vrf_name_rand = f'{choice(ascii_lowercase)}{vrf_name}'[:15]
            if vrf_name_rand not in vrfs_current:
                vrf_name_new = vrf_name_rand
        # Update VRF name to a new one
        config.rename(['vrf', 'name', vrf_name], vrf_name_new)
        # Check config items with old VRF name and replace to new one
        config_commands = config.to_commands().split('\n')
        vrf_config_lines = _search_vrfs(config_commands, vrf_name)
        # Rename VRF to a new name
        if vrf_config_lines:
            for vrf_value_path in vrf_config_lines:
                config.set(vrf_value_path, vrf_name_new, replace=True)

try:
    with open(file_name, 'w') as f:
        f.write(config.to_string())
except OSError as e:
    print("Failed to save the modified config: {}".format(e))
    exit(1)