1 package PVE
::Network
::SDN
;
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
;
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);
21 my $running_cfg = "sdn/.running-config";
23 my $parse_running_cfg = sub {
24 my ($filename, $raw) = @_;
28 return $cfg if !defined($raw) || $raw eq '';
31 $cfg = from_json
($raw);
38 my $write_running_cfg = sub {
39 my ($filename, $cfg) = @_;
41 my $json = to_json
($cfg);
46 PVE
::Cluster
::cfs_register_file
($running_cfg, $parse_running_cfg, $write_running_cfg);
49 # improve me : move status code inside plugins ?
53 my $cmd = ['ifquery', '-a', '-c', '-o','json'];
56 my $reader = sub { $result .= shift };
59 run_command
($cmd, outfunc
=> $reader);
62 my $resultjson = decode_json
($result);
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
},
79 my ($zone_status, $vnet_status) = PVE
::Network
::SDN
::Zones
::status
();
80 return($zone_status, $vnet_status);
84 return cfs_read_file
($running_cfg);
88 my ($running_cfg, $cfg, $type) = @_;
92 my $running_objects = $running_cfg->{$type}->{ids
};
93 my $config_objects = $cfg->{ids
};
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";
110 $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"});
113 foreach my $id (sort keys %{$config_objects}) {
114 my $running_object = $running_objects->{$id};
115 my $config_object = $config_objects->{$id};
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;
123 $pending->{$id}->{"pending"}->{$key} = $config_value if !defined($running_value) || ($config_value ne $running_value);
125 if(!keys %{$running_object}) {
126 $pending->{$id}->{state} = "new";
127 } elsif (!defined($running_value) && defined($config_value)) {
128 $pending->{$id}->{state} = "changed";
131 $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"});
134 return {ids
=> $pending};
140 my $cfg = cfs_read_file
($running_cfg);
141 my $version = $cfg->{version
};
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
();
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
} };
159 $cfg = { version
=> $version, vnets
=> $vnets, zones
=> $zones, controllers
=> $controllers, subnets
=> $subnets };
161 cfs_write_file
($running_cfg, $cfg);
164 sub lock_sdn_config
{
165 my ($code, $errmsg) = @_;
167 cfs_lock_file
($running_cfg, undef, $code);
170 $errmsg ?
die "$errmsg: $err" : die $err;
174 sub get_local_vnets
{
176 my $rpcenv = PVE
::RPCEnvironment
::get
();
178 my $authuser = $rpcenv->get_user();
180 my $nodename = PVE
::INotify
::nodename
();
182 my $cfg = PVE
::Network
::SDN
::running_config
();
183 my $vnets_cfg = $cfg->{vnets
};
184 my $zones_cfg = $cfg->{zones
};
186 my @vnetids = PVE
::Network
::SDN
::Vnets
::sdn_vnets_ids
($vnets_cfg);
190 foreach my $vnetid (@vnetids) {
192 my $vnet = PVE
::Network
::SDN
::Vnets
::sdn_vnets_config
($vnets_cfg, $vnetid);
193 my $zoneid = $vnet->{zone
};
194 my $comments = $vnet->{alias
};
196 my $privs = [ 'SDN.Audit', 'SDN.Use' ];
199 next if !$rpcenv->check_sdn_bridge($authuser, $zoneid, $vnetid, $privs, 1);
201 my $zone_config = PVE
::Network
::SDN
::Zones
::sdn_zones_config
($zones_cfg, $zoneid);
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 };
212 sub generate_zone_config
{
213 my $raw_config = PVE
::Network
::SDN
::Zones
::generate_etc_network_config
();
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;
221 log_warn
("Failed to read network interfaces definition - $@") if $@;
223 PVE
::Network
::SDN
::Zones
::write_etc_network_config
($raw_config);
226 sub generate_controller_config
{
229 my $raw_config = PVE
::Network
::SDN
::Controllers
::generate_controller_config
();
230 PVE
::Network
::SDN
::Controllers
::write_controller_config
($raw_config);
232 PVE
::Network
::SDN
::Controllers
::reload_controller
() if $reload;
235 sub generate_dhcp_config
{
238 PVE
::Network
::SDN
::Dhcp
::regenerate_config
($reload);
242 my ($type, $key, $value) = @_;
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);
260 my ($method, $url, $headers, $data) = @_;
262 my $encoded_data = to_json
($data) if $data;
264 my $req = HTTP
::Request-
>new($method,$url, $headers, $encoded_data);
266 my $ua = LWP
::UserAgent-
>new(protocols_allowed
=> ['http', 'https'], timeout
=> 30);
270 $ua->proxy(['http', 'https'], $proxy);
275 $ua->ssl_opts(verify_hostname
=> 0, SSL_verify_mode
=> 0x00);
277 my $response = $ua->request($req);
278 my $code = $response->code;
280 if ($code !~ /^2(\d+)$/) {
281 my $msg = $response->message || 'unknown';
282 die "Invalid response from server: $code $msg\n";
286 if (defined($response->decoded_content)) {
287 $raw = $response->decoded_content;
289 $raw = $response->content;
291 return if $raw eq '';
293 my $res = eval { from_json
($raw) };
294 die "api response is not a json" if $@;