]> git.proxmox.com Git - pve-network.git/blame - PVE/Network/SDN/Zones/Plugin.pm
add tap|veth create|plug
[pve-network.git] / PVE / Network / SDN / Zones / Plugin.pm
CommitLineData
f5eabba0 1package PVE::Network::SDN::Zones::Plugin;
6939693f
AD
2
3use strict;
4use warnings;
5
1f543c5f 6use PVE::Tools qw(run_command);
6939693f
AD
7use PVE::JSONSchema;
8use PVE::Cluster;
eb1549e7 9use PVE::Network;
6939693f
AD
10
11use Data::Dumper;
eec580bf 12use PVE::JSONSchema qw(get_standard_option);
6939693f
AD
13use base qw(PVE::SectionConfig);
14
f5eabba0 15PVE::Cluster::cfs_register_file('sdn/zones.cfg',
39d04c82
AD
16 sub { __PACKAGE__->parse_config(@_); });
17
f5eabba0 18PVE::Cluster::cfs_register_file('sdn/zones.cfg.new',
6939693f
AD
19 sub { __PACKAGE__->parse_config(@_); },
20 sub { __PACKAGE__->write_config(@_); });
21
f5eabba0
AD
22PVE::JSONSchema::register_standard_option('pve-sdn-zone-id', {
23 description => "The SDN zone object identifier.",
24 type => 'string', format => 'pve-sdn-zone-id',
fe61b14c
AD
25});
26
f5eabba0
AD
27PVE::JSONSchema::register_format('pve-sdn-zone-id', \&parse_sdn_zone_id);
28sub parse_sdn_zone_id {
29 my ($id, $noerr) = @_;
fe61b14c 30
7c5b0f6d
AD
31 if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) {
32 return undef if $noerr;
33 die "zone ID '$id' contains illegal characters\n";
fe61b14c 34 }
7c5b0f6d 35 die "zone ID '$id' can't be more length than 10 characters\n" if length($id) > 10;
f5eabba0 36 return $id;
fe61b14c
AD
37}
38
6939693f
AD
39my $defaultData = {
40
41 propertyList => {
7d35eaf5 42 type => {
6939693f
AD
43 description => "Plugin type.",
44 type => 'string', format => 'pve-configid',
45 type => 'string',
46 },
c2b9c173 47 nodes => get_standard_option('pve-node-list', { optional => 1 }),
f5eabba0
AD
48 zone => get_standard_option('pve-sdn-zone-id',
49 { completion => \&PVE::Network::SDN::Zones::complete_sdn_zone }),
6939693f
AD
50 },
51};
52
53sub private {
54 return $defaultData;
55}
56
c2b9c173
AD
57sub decode_value {
58 my ($class, $type, $key, $value) = @_;
59
60 if ($key eq 'nodes') {
61 my $res = {};
62
63 foreach my $node (PVE::Tools::split_list($value)) {
64 if (PVE::JSONSchema::pve_verify_node_name($node)) {
65 $res->{$node} = 1;
66 }
67 }
68
69 return $res;
70 }
71
72 return $value;
73}
74
75sub encode_value {
76 my ($class, $type, $key, $value) = @_;
77
78 if ($key eq 'nodes') {
79 return join(',', keys(%$value));
80 }
81
82 return $value;
83}
84
6939693f
AD
85sub parse_section_header {
86 my ($class, $line) = @_;
87
88 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
f5eabba0 89 my ($type, $id) = (lc($1), $2);
6939693f
AD
90 my $errmsg = undef; # set if you want to skip whole section
91 eval { PVE::JSONSchema::pve_verify_configid($type); };
92 $errmsg = $@ if $@;
93 my $config = {}; # to return additional attributes
f5eabba0 94 return ($type, $id, $errmsg, $config);
6939693f
AD
95 }
96 return undef;
97}
98
6bffe819 99sub generate_sdn_config {
ba7ac021 100 my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $interfaces_config, $config) = @_;
6939693f
AD
101
102 die "please implement inside plugin";
103}
104
8fb1ee7f 105sub generate_controller_config {
56cdcac9 106 my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
32602a38
AD
107
108 die "please implement inside plugin";
109}
110
ad03c543 111sub generate_controller_vnet_config {
56cdcac9 112 my ($class, $plugin_config, $controller, $zoneid, $vnetid, $config) = @_;
ad03c543
AD
113
114}
115
8fb1ee7f
AD
116sub write_controller_config {
117 my ($class, $plugin_config, $config) = @_;
118
119 die "please implement inside plugin";
120}
121
fa609bdd
AD
122sub controller_reload {
123 my ($class) = @_;
124
125 die "please implement inside plugin";
126}
127
fe0c6b9e 128sub on_delete_hook {
56cdcac9 129 my ($class, $zoneid, $vnet_cfg) = @_;
e8d5906e 130
56cdcac9
AD
131 # verify that no vnet are associated to this zone
132 foreach my $id (keys %{$vnet_cfg->{ids}}) {
133 my $vnet = $vnet_cfg->{ids}->{$id};
134 die "zone $zoneid is used by vnet $id"
135 if ($vnet->{type} eq 'vnet' && defined($vnet->{zone}) && $vnet->{zone} eq $zoneid);
136 }
e8d5906e
AD
137}
138
139sub on_update_hook {
a2b32a94 140 my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_;
fe0c6b9e
AD
141
142 # do nothing by default
143}
144
6939693f
AD
145#helpers
146sub parse_tag_number_or_range {
147 my ($str, $max, $tag) = @_;
148
149 my @elements = split(/,/, $str);
150 my $count = 0;
151 my $allowed = undef;
152
153 die "extraneous commas in list\n" if $str ne join(',', @elements);
154 foreach my $item (@elements) {
155 if ($item =~ m/^([0-9]+)-([0-9]+)$/) {
156 $count += 2;
157 my ($port1, $port2) = ($1, $2);
158 die "invalid port '$port1'\n" if $port1 > $max;
159 die "invalid port '$port2'\n" if $port2 > $max;
160 die "backwards range '$port1:$port2' not allowed, did you mean '$port2:$port1'?\n" if $port1 > $port2;
161
162 if ($tag && $tag >= $port1 && $tag <= $port2){
163 $allowed = 1;
164 last;
165 }
166
167 } elsif ($item =~ m/^([0-9]+)$/) {
168 $count += 1;
169 my $port = $1;
170 die "invalid port '$port'\n" if $port > $max;
171
172 if ($tag && $tag == $port){
173 $allowed = 1;
174 last;
175 }
176 }
177 }
178 die "tag $tag is not allowed" if $tag && !$allowed;
179
180 return (scalar(@elements) > 1);
181}
182
627b1694
AD
183sub status {
184 my ($class, $plugin_config, $zone, $id, $vnet, $err_config, $status, $vnet_status, $zone_status) = @_;
185
186 $vnet_status->{$id}->{zone} = $zone;
187 $zone_status->{$zone}->{status} = 'available' if !defined($zone_status->{$zone}->{status});
188
189 if($err_config) {
190 $vnet_status->{$id}->{status} = 'pending';
191 $vnet_status->{$id}->{statusmsg} = $err_config;
192 $zone_status->{$zone}->{status} = 'pending';
193 } elsif ($status->{$id}->{status} && $status->{$id}->{status} eq 'pass') {
194 $vnet_status->{$id}->{status} = 'available';
195 my $bridgeport = $status->{$id}->{config}->{'bridge-ports'};
196
197 if ($bridgeport && $status->{$bridgeport}->{status} && $status->{$bridgeport}->{status} ne 'pass') {
198 $vnet_status->{$id}->{status} = 'error';
199 $vnet_status->{$id}->{statusmsg} = 'configuration not fully applied';
200 $zone_status->{$zone}->{status} = 'error';
201 }
202
203 } else {
204 $vnet_status->{$id}->{status} = 'error';
205 $vnet_status->{$id}->{statusmsg} = 'missing';
206 $zone_status->{$zone}->{status} = 'error';
207 }
208}
209
2ba9613b
AD
210
211sub get_bridge_vlan {
eb1549e7 212 my ($class, $plugin_config, $vnetid, $tag) = @_;
2ba9613b 213
eb1549e7 214 my $bridge = $vnetid;
2ba9613b
AD
215 $tag = undef;
216
eb1549e7
AD
217 die "bridge $bridge is missing" if !-d "/sys/class/net/$bridge/";
218
2ba9613b
AD
219 return ($bridge, $tag);
220}
221
eb1549e7
AD
222sub tap_create {
223 my ($class, $plugin_config, $vnet, $iface, $vnetid) = @_;
224
225 my ($bridge, undef) = $class->get_bridge_vlan($plugin_config, $vnetid);
226 die "unable to get bridge setting\n" if !$bridge;
227
228 PVE::Network::tap_create($iface, $bridge);
229}
230
231sub veth_create {
232 my ($class, $plugin_config, $vnet, $veth, $vethpeer, $vnetid, $hwaddr) = @_;
233
234 my ($bridge, undef) = $class->get_bridge_vlan($plugin_config, $vnetid);
235 die "unable to get bridge setting\n" if !$bridge;
236
237 PVE::Network::veth_create($veth, $vethpeer, $bridge, $hwaddr);
238}
239
240sub tap_plug {
241 my ($class, $plugin_config, $vnet, $iface, $vnetid, $firewall, $rate) = @_;
242
243 my $tag = $vnet->{tag};
244
245 ($vnetid, $tag) = $class->get_bridge_vlan($plugin_config, $vnetid, $tag);
246 my $trunks = undef;
247
248 PVE::Network::tap_plug($iface, $vnetid, $tag, $firewall, $trunks, $rate);
249}
250
3794e429
AD
251#helper
252
253sub get_uplink_iface {
254 my ($interfaces_config, $uplink) = @_;
255
256 my $iface = undef;
257 foreach my $id (keys %{$interfaces_config->{ifaces}}) {
258 my $interface = $interfaces_config->{ifaces}->{$id};
259 if (my $iface_uplink = $interface->{'uplink-id'}) {
260 next if $iface_uplink ne $uplink;
261 if($interface->{type} ne 'eth' && $interface->{type} ne 'bond') {
262 warn "uplink $uplink is not a physical or bond interface";
263 next;
264 }
265 $iface = $id;
266 }
267 }
268
269 #create a dummy uplink interface if no uplink found
270 if(!$iface) {
271 warn "can't find uplink $uplink in physical interface";
272 $iface = "uplink${uplink}";
273 }
274
275 return $iface;
276}
1f543c5f
AD
277
278sub get_local_route_ip {
279 my ($targetip) = @_;
280
281 my $ip = undef;
282 my $interface = undef;
283
284 run_command(['/sbin/ip', 'route', 'get', $targetip], outfunc => sub {
285 if ($_[0] =~ m/src ($PVE::Tools::IPRE)/) {
286 $ip = $1;
287 }
288 if ($_[0] =~ m/dev (\S+)/) {
289 $interface = $1;
290 }
291
292 });
293 return ($ip, $interface);
294}
295
296
297sub find_local_ip_interface_peers {
298 my ($peers) = @_;
299
300 my $network_config = PVE::INotify::read_file('interfaces');
301 my $ifaces = $network_config->{ifaces};
302 #is a local ip member of peers list ?
303 foreach my $address (@{$peers}) {
304 while (my $interface = each %$ifaces) {
305 my $ip = $ifaces->{$interface}->{address};
306 if ($ip && $ip eq $address) {
307 return ($ip, $interface);
308 }
309 }
310 }
311
312 #if peer is remote, find source with ip route
313 foreach my $address (@{$peers}) {
314 my ($ip, $interface) = get_local_route_ip($address);
315 return ($ip, $interface);
316 }
317}
318
6939693f 3191;