]> git.proxmox.com Git - pve-network.git/blob - src/PVE/Network/SDN.pm
sdn: config generation : check if interfaces.d/sdn is sourced
[pve-network.git] / src / 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::INotify;
10
11 use PVE::Network::SDN::Vnets;
12 use PVE::Network::SDN::Zones;
13 use PVE::Network::SDN::Controllers;
14 use PVE::Network::SDN::Subnets;
15
16 use PVE::Tools qw(extract_param dir_glob_regex run_command);
17 use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
18 use PVE::RESTEnvironment qw(log_warn);
19
20 my $running_cfg = "sdn/.running-config";
21
22 my $parse_running_cfg = sub {
23 my ($filename, $raw) = @_;
24
25 my $cfg = {};
26
27 return $cfg if !defined($raw) || $raw eq '';
28
29 eval {
30 $cfg = from_json($raw);
31 };
32 return {} if $@;
33
34 return $cfg;
35 };
36
37 my $write_running_cfg = sub {
38 my ($filename, $cfg) = @_;
39
40 my $json = to_json($cfg);
41
42 return $json;
43 };
44
45 PVE::Cluster::cfs_register_file($running_cfg, $parse_running_cfg, $write_running_cfg);
46
47
48 # improve me : move status code inside plugins ?
49
50 sub ifquery_check {
51
52 my $cmd = ['ifquery', '-a', '-c', '-o','json'];
53
54 my $result = '';
55 my $reader = sub { $result .= shift };
56
57 eval {
58 run_command($cmd, outfunc => $reader);
59 };
60
61 my $resultjson = decode_json($result);
62 my $interfaces = {};
63
64 foreach my $interface (@$resultjson) {
65 my $name = $interface->{name};
66 $interfaces->{$name} = {
67 status => $interface->{status},
68 config => $interface->{config},
69 config_status => $interface->{config_status},
70 };
71 }
72
73 return $interfaces;
74 }
75
76 sub status {
77
78 my ($zone_status, $vnet_status) = PVE::Network::SDN::Zones::status();
79 return($zone_status, $vnet_status);
80 }
81
82 sub running_config {
83 return cfs_read_file($running_cfg);
84 }
85
86 sub pending_config {
87 my ($running_cfg, $cfg, $type) = @_;
88
89 my $pending = {};
90
91 my $running_objects = $running_cfg->{$type}->{ids};
92 my $config_objects = $cfg->{ids};
93
94 foreach my $id (sort keys %{$running_objects}) {
95 my $running_object = $running_objects->{$id};
96 my $config_object = $config_objects->{$id};
97 foreach my $key (sort keys %{$running_object}) {
98 $pending->{$id}->{$key} = $running_object->{$key};
99 if(!keys %{$config_object}) {
100 $pending->{$id}->{state} = "deleted";
101 } elsif (!defined($config_object->{$key})) {
102 $pending->{$id}->{"pending"}->{$key} = 'deleted';
103 $pending->{$id}->{state} = "changed";
104 } elsif (PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key})
105 ne PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key})) {
106 $pending->{$id}->{state} = "changed";
107 }
108 }
109 $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"});
110 }
111
112 foreach my $id (sort keys %{$config_objects}) {
113 my $running_object = $running_objects->{$id};
114 my $config_object = $config_objects->{$id};
115
116 foreach my $key (sort keys %{$config_object}) {
117 my $config_value = PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key}) if $config_object->{$key};
118 my $running_value = PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key}) if $running_object->{$key};
119 if($key eq 'type' || $key eq 'vnet') {
120 $pending->{$id}->{$key} = $config_value;
121 } else {
122 $pending->{$id}->{"pending"}->{$key} = $config_value if !defined($running_value) || ($config_value ne $running_value);
123 }
124 if(!keys %{$running_object}) {
125 $pending->{$id}->{state} = "new";
126 } elsif (!defined($running_value) && defined($config_value)) {
127 $pending->{$id}->{state} = "changed";
128 }
129 }
130 $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"});
131 }
132
133 return {ids => $pending};
134
135 }
136
137 sub commit_config {
138
139 my $cfg = cfs_read_file($running_cfg);
140 my $version = $cfg->{version};
141
142 if ($version) {
143 $version++;
144 } else {
145 $version = 1;
146 }
147
148 my $vnets_cfg = PVE::Network::SDN::Vnets::config();
149 my $zones_cfg = PVE::Network::SDN::Zones::config();
150 my $controllers_cfg = PVE::Network::SDN::Controllers::config();
151 my $subnets_cfg = PVE::Network::SDN::Subnets::config();
152
153 my $vnets = { ids => $vnets_cfg->{ids} };
154 my $zones = { ids => $zones_cfg->{ids} };
155 my $controllers = { ids => $controllers_cfg->{ids} };
156 my $subnets = { ids => $subnets_cfg->{ids} };
157
158 $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets };
159
160 cfs_write_file($running_cfg, $cfg);
161 }
162
163 sub lock_sdn_config {
164 my ($code, $errmsg) = @_;
165
166 cfs_lock_file($running_cfg, undef, $code);
167
168 if (my $err = $@) {
169 $errmsg ? die "$errmsg: $err" : die $err;
170 }
171 }
172
173 sub get_local_vnets {
174
175 my $rpcenv = PVE::RPCEnvironment::get();
176
177 my $authuser = $rpcenv->get_user();
178
179 my $nodename = PVE::INotify::nodename();
180
181 my $cfg = PVE::Network::SDN::running_config();
182 my $vnets_cfg = $cfg->{vnets};
183 my $zones_cfg = $cfg->{zones};
184
185 my @vnetids = PVE::Network::SDN::Vnets::sdn_vnets_ids($vnets_cfg);
186
187 my $vnets = {};
188
189 foreach my $vnetid (@vnetids) {
190
191 my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($vnets_cfg, $vnetid);
192 my $zoneid = $vnet->{zone};
193 my $comments = $vnet->{alias};
194
195 my $privs = [ 'SDN.Audit', 'SDN.Use' ];
196
197 next if !$zoneid;
198 next if !$rpcenv->check_sdn_bridge($authuser, $zoneid, $vnetid, $privs, 1);
199
200 my $zone_config = PVE::Network::SDN::Zones::sdn_zones_config($zones_cfg, $zoneid);
201
202 next if defined($zone_config->{nodes}) && !$zone_config->{nodes}->{$nodename};
203 my $ipam = $zone_config->{ipam} ? 1 : 0;
204 my $vlanaware = $vnet->{vlanaware} ? 1 : 0;
205 $vnets->{$vnetid} = { type => 'vnet', active => '1', ipam => $ipam, vlanaware => $vlanaware, comments => $comments };
206 }
207
208 return $vnets;
209 }
210
211 sub generate_zone_config {
212 my $raw_config = PVE::Network::SDN::Zones::generate_etc_network_config();
213 if ($raw_config) {
214 eval {
215 my $net_cfg = PVE::INotify::read_file('interfaces', 1);
216 my $opts = $net_cfg->{data}->{options};
217 log_warn("missing 'source /etc/network/interfaces.d/sdn' directive for SDN support!\n")
218 if ! grep { $_->[1] =~ m!^source /etc/network/interfaces.d/(:?sdn|\*)! } @$opts;
219 };
220 log_warn("Failed to read network interfaces definition - $@") if $@;
221 }
222 PVE::Network::SDN::Zones::write_etc_network_config($raw_config);
223 }
224
225 sub generate_controller_config {
226 my ($reload) = @_;
227
228 my $raw_config = PVE::Network::SDN::Controllers::generate_controller_config();
229 PVE::Network::SDN::Controllers::write_controller_config($raw_config);
230
231 PVE::Network::SDN::Controllers::reload_controller() if $reload;
232 }
233
234 sub encode_value {
235 my ($type, $key, $value) = @_;
236
237 if ($key eq 'nodes' || $key eq 'exitnodes') {
238 if(ref($value) eq 'HASH') {
239 return join(',', sort keys(%$value));
240 } else {
241 return $value;
242 }
243 }
244
245 return $value;
246 }
247
248
249 #helpers
250 sub api_request {
251 my ($method, $url, $headers, $data) = @_;
252
253 my $encoded_data = to_json($data) if $data;
254
255 my $req = HTTP::Request->new($method,$url, $headers, $encoded_data);
256
257 my $ua = LWP::UserAgent->new(protocols_allowed => ['http', 'https'], timeout => 30);
258 my $proxy = undef;
259
260 if ($proxy) {
261 $ua->proxy(['http', 'https'], $proxy);
262 } else {
263 $ua->env_proxy;
264 }
265
266 $ua->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0x00);
267
268 my $response = $ua->request($req);
269 my $code = $response->code;
270
271 if ($code !~ /^2(\d+)$/) {
272 my $msg = $response->message || 'unknown';
273 die "Invalid response from server: $code $msg\n";
274 }
275
276 my $raw = '';
277 if (defined($response->decoded_content)) {
278 $raw = $response->decoded_content;
279 } else {
280 $raw = $response->content;
281 }
282
283 return if $raw eq '';
284
285 my $json = '';
286 eval {
287 $json = from_json($raw);
288 };
289 die "api response is not a json" if $@;
290
291 return $json;
292 }
293
294 1;