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