]> git.proxmox.com Git - pve-network.git/blob - PVE/Network/SDN.pm
sdn: add generate_frr_config
[pve-network.git] / PVE / Network / SDN.pm
1 package PVE::Network::SDN;
2
3 use strict;
4 use warnings;
5
6 use Data::Dumper;
7 use JSON;
8
9 use PVE::Tools qw(extract_param dir_glob_regex run_command);
10 use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
11 use PVE::Network::SDN::Plugin;
12 use PVE::Network::SDN::VnetPlugin;
13 use PVE::Network::SDN::VlanPlugin;
14 use PVE::Network::SDN::VxlanPlugin;
15 use PVE::Network::SDN::FrrPlugin;
16
17 PVE::Network::SDN::VnetPlugin->register();
18 PVE::Network::SDN::VlanPlugin->register();
19 PVE::Network::SDN::VxlanPlugin->register();
20 PVE::Network::SDN::FrrPlugin->register();
21 PVE::Network::SDN::Plugin->init();
22
23
24 sub sdn_config {
25 my ($cfg, $sdnid, $noerr) = @_;
26
27 die "no sdn ID specified\n" if !$sdnid;
28
29 my $scfg = $cfg->{ids}->{$sdnid};
30 die "sdn '$sdnid' does not exists\n" if (!$noerr && !$scfg);
31
32 return $scfg;
33 }
34
35 sub config {
36 my $config = cfs_read_file("sdn.cfg.new");
37 $config = cfs_read_file("sdn.cfg") if !keys %{$config->{ids}};
38 return $config;
39 }
40
41 sub write_config {
42 my ($cfg) = @_;
43
44 cfs_write_file("sdn.cfg.new", $cfg);
45 }
46
47 sub lock_sdn_config {
48 my ($code, $errmsg) = @_;
49
50 cfs_lock_file("sdn.cfg.new", undef, $code);
51 if (my $err = $@) {
52 $errmsg ? die "$errmsg: $err" : die $err;
53 }
54 }
55
56 sub sdn_ids {
57 my ($cfg) = @_;
58
59 return keys %{$cfg->{ids}};
60 }
61
62 sub complete_sdn {
63 my ($cmdname, $pname, $cvalue) = @_;
64
65 my $cfg = PVE::Network::SDN::config();
66
67 return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_ids($cfg) ];
68 }
69
70 sub ifquery_check {
71
72 my $cmd = ['ifquery', '-a', '-c', '-o','json'];
73
74 my $result = '';
75 my $reader = sub { $result .= shift };
76
77 eval {
78 run_command($cmd, outfunc => $reader);
79 };
80
81 my $resultjson = decode_json($result);
82 my $interfaces = {};
83
84 foreach my $interface (@$resultjson) {
85 my $name = $interface->{name};
86 $interfaces->{$name} = {
87 status => $interface->{status},
88 config => $interface->{config},
89 config_status => $interface->{config_status},
90 };
91 }
92
93 return $interfaces;
94 }
95
96
97 sub generate_etc_network_config {
98
99 my $sdn_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
100 return if !$sdn_cfg;
101
102 #read main config for physical interfaces
103 my $current_config_file = "/etc/network/interfaces";
104 my $fh = IO::File->new($current_config_file);
105 my $interfaces_config = PVE::INotify::read_etc_network_interfaces(1,$fh);
106 $fh->close();
107
108 #check uplinks
109 my $uplinks = {};
110 foreach my $id (keys %{$interfaces_config->{ifaces}}) {
111 my $interface = $interfaces_config->{ifaces}->{$id};
112 if (my $uplink = $interface->{'uplink-id'}) {
113 die "uplink-id $uplink is already defined on $uplinks->{$uplink}" if $uplinks->{$uplink};
114 $interface->{name} = $id;
115 $uplinks->{$interface->{'uplink-id'}} = $interface;
116 }
117 }
118
119 my $vnet_cfg = undef;
120 my $transport_cfg = undef;
121
122 foreach my $id (keys %{$sdn_cfg->{ids}}) {
123 if ($sdn_cfg->{ids}->{$id}->{type} eq 'vnet') {
124 $vnet_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
125 } else {
126 $transport_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
127 }
128 }
129
130 #generate configuration
131 my $config = {};
132 foreach my $id (keys %{$vnet_cfg->{ids}}) {
133 my $vnet = $vnet_cfg->{ids}->{$id};
134 my $zone = $vnet->{transportzone};
135
136 if(!$zone) {
137 warn "can't generate vnet $vnet : zone $zone don't exist";
138 next;
139 }
140
141 my $plugin_config = $transport_cfg->{ids}->{$zone};
142
143 if (!defined($plugin_config)) {
144 warn "can't generate vnet $vnet : zone $zone don't exist";
145 next;
146 }
147
148 my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
149 $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $uplinks, $config);
150 }
151
152 my $raw_network_config = "";
153 foreach my $iface (keys %$config) {
154 $raw_network_config .= "\n";
155 $raw_network_config .= "auto $iface\n";
156 $raw_network_config .= "iface $iface\n";
157 foreach my $option (@{$config->{$iface}}) {
158 $raw_network_config .= "\t$option\n";
159 }
160 }
161
162 return $raw_network_config;
163 }
164
165 sub generate_frr_config {
166
167 my $sdn_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
168 return if !$sdn_cfg;
169
170 #read main config for physical interfaces
171 my $current_config_file = "/etc/network/interfaces";
172 my $fh = IO::File->new($current_config_file);
173 my $interfaces_config = PVE::INotify::read_etc_network_interfaces(1,$fh);
174 $fh->close();
175
176 #check uplinks
177 my $uplinks = {};
178 foreach my $id (keys %{$interfaces_config->{ifaces}}) {
179 my $interface = $interfaces_config->{ifaces}->{$id};
180 if (my $uplink = $interface->{'uplink-id'}) {
181 die "uplink-id $uplink is already defined on $uplinks->{$uplink}" if $uplinks->{$uplink};
182 $interface->{name} = $id;
183 $uplinks->{$interface->{'uplink-id'}} = $interface;
184 }
185 }
186
187 my $frr_cfg = undef;
188 my $transport_cfg = undef;
189
190 foreach my $id (keys %{$sdn_cfg->{ids}}) {
191 if ($sdn_cfg->{ids}->{$id}->{type} eq 'frr') {
192 $frr_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
193 } elsif ($sdn_cfg->{ids}->{$id}->{type} ne 'vnet') {
194 $transport_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
195 }
196 }
197
198 #generate configuration
199 my $config = {};
200
201 foreach my $id (keys %{$frr_cfg->{ids}}) {
202 my $plugin_config = $frr_cfg->{ids}->{$id};
203 my $asn = $plugin_config->{asn};
204 if ($asn) {
205 my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
206 $plugin->generate_frr_config($plugin_config, $asn, $id, $uplinks, $config);
207 }
208 }
209
210 foreach my $id (keys %{$transport_cfg->{ids}}) {
211 my $plugin_config = $transport_cfg->{ids}->{$id};
212 my $router = $plugin_config->{router};
213 if ($router) {
214 my $asn = $frr_cfg->{ids}->{$router}->{asn};
215 if ($asn) {
216 my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
217 $plugin->generate_frr_config($plugin_config, $asn, $id, $uplinks, $config);
218 }
219 }
220 }
221
222 my $raw_frr_config = "log syslog informational\n";
223 $raw_frr_config .= "!\n";
224
225 #vrf first
226 my $vrfconfig = $config->{vrf};
227 foreach my $vrf (sort keys %$vrfconfig) {
228 $raw_frr_config .= "$vrf\n";
229 foreach my $option (@{$vrfconfig->{$vrf}}) {
230 $raw_frr_config .= " $option\n";
231 }
232 $raw_frr_config .= "!\n";
233 }
234
235 #routers
236 my $routerconfig = $config->{router};
237 foreach my $router (sort keys %$routerconfig) {
238 $raw_frr_config .= "$router\n";
239 foreach my $option (@{$routerconfig->{$router}}) {
240 $raw_frr_config .= " $option\n";
241 }
242 $raw_frr_config .= "!\n";
243 }
244
245 $raw_frr_config .= "line vty\n";
246 $raw_frr_config .= "!\n";
247
248 return $raw_frr_config;
249 }
250
251 sub write_etc_network_config {
252 my ($rawconfig) = @_;
253
254 return if !$rawconfig;
255 my $sdn_interfaces_file = "/etc/network/interfaces.d/sdn";
256
257 my $writefh = IO::File->new($sdn_interfaces_file,">");
258 print $writefh $rawconfig;
259 $writefh->close();
260 }
261
262
263 sub status {
264
265 my $cluster_sdn_file = "/etc/pve/sdn.cfg";
266 my $local_sdn_file = "/etc/network/interfaces.d/sdn";
267 my $err_config = undef;
268
269 return if !-e $cluster_sdn_file;
270
271 if (!-e $local_sdn_file) {
272 warn "local sdn network configuration is not yet generated, please reload";
273 $err_config = 'pending';
274 } else {
275 # fixme : use some kind of versioning info?
276 my $cluster_sdn_timestamp = (stat($cluster_sdn_file))[9];
277 my $local_sdn_timestamp = (stat($local_sdn_file))[9];
278
279 if ($local_sdn_timestamp < $cluster_sdn_timestamp) {
280 warn "local sdn network configuration is too old, please reload";
281 $err_config = 'unknown';
282 }
283 }
284
285 my $status = ifquery_check();
286
287 my $network_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
288 my $vnet_cfg = undef;
289 my $transport_cfg = undef;
290
291 my $vnet_status = {};
292 my $transport_status = {};
293
294 foreach my $id (keys %{$network_cfg->{ids}}) {
295 if ($network_cfg->{ids}->{$id}->{type} eq 'vnet') {
296 my $transportzone = $network_cfg->{ids}->{$id}->{transportzone};
297 $vnet_status->{$id}->{transportzone} = $transportzone;
298 $transport_status->{$transportzone}->{status} = 'available' if !defined($transport_status->{$transportzone}->{status});
299
300 if($err_config) {
301 $vnet_status->{$id}->{status} = $err_config;
302 $transport_status->{$transportzone}->{status} = $err_config;
303 } elsif ($status->{$id}->{status} && $status->{$id}->{status} eq 'pass') {
304 $vnet_status->{$id}->{status} = 'available';
305 my $bridgeport = $status->{$id}->{config}->{'bridge-ports'};
306
307 if ($status->{$bridgeport}->{status} && $status->{$bridgeport}->{status} ne 'pass') {
308 $vnet_status->{$id}->{status} = 'error';
309 $transport_status->{$transportzone}->{status} = 'error';
310 }
311 } else {
312 $vnet_status->{$id}->{status} = 'error';
313 $transport_status->{$transportzone}->{status} = 'error';
314 }
315 }
316 }
317 return($transport_status, $vnet_status);
318 }
319
320 1;
321