]>
Commit | Line | Data |
---|---|---|
aff192e6 DM |
1 | package PVE::API2::Network; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use PVE::Tools qw(extract_param); | |
7 | use PVE::SafeSyslog; | |
8 | use PVE::INotify; | |
9 | use PVE::Exception qw(raise_param_exc); | |
10 | use PVE::RESTHandler; | |
11 | use PVE::RPCEnvironment; | |
12 | use PVE::JSONSchema qw(get_standard_option); | |
13 | use PVE::AccessControl; | |
14 | use IO::File; | |
15 | ||
16 | use base qw(PVE::RESTHandler); | |
17 | ||
18 | my $iflockfn = "/etc/network/.pve-interfaces.lock"; | |
19 | ||
20 | my $bond_mode_enum = [ | |
21 | 'balance-rr', | |
22 | 'active-backup', | |
23 | 'balance-xor', | |
24 | 'broadcast', | |
25 | '802.3ad', | |
26 | 'balance-tlb', | |
27 | 'balance-alb' | |
28 | ]; | |
29 | ||
30 | my $confdesc = { | |
31 | autostart => { | |
32 | description => "Automatically start interface on boot.", | |
33 | type => 'boolean', | |
34 | optional => 1, | |
35 | }, | |
36 | bridge_ports => { | |
37 | description => "Specify the iterfaces you want to add to your bridge.", | |
38 | optional => 1, | |
39 | type => 'string', format => 'pve-iface-list', | |
40 | }, | |
41 | slaves => { | |
42 | description => "Specify the interfaces used by the bonding device.", | |
43 | optional => 1, | |
44 | type => 'string', format => 'pve-iface-list', | |
45 | }, | |
46 | bond_mode => { | |
47 | description => "Bonding mode.", | |
48 | optional => 1, | |
49 | type => 'string', enum => $bond_mode_enum, | |
50 | }, | |
51 | gateway => { | |
52 | description => 'Default gateway address.', | |
53 | type => 'string', format => 'ipv4', | |
54 | optional => 1, | |
55 | }, | |
56 | netmask => { | |
57 | description => 'Network mask.', | |
58 | type => 'string', format => 'ipv4mask', | |
59 | optional => 1, | |
60 | }, | |
61 | address => { | |
62 | description => 'IP address.', | |
63 | type => 'string', format => 'ipv4', | |
64 | optional => 1, | |
65 | requires => 'netmask', | |
66 | } | |
67 | }; | |
68 | ||
69 | sub json_config_properties { | |
70 | my $prop = shift; | |
71 | ||
72 | foreach my $opt (keys %$confdesc) { | |
73 | $prop->{$opt} = $confdesc->{$opt}; | |
74 | } | |
75 | ||
76 | return $prop; | |
77 | } | |
78 | ||
79 | __PACKAGE__->register_method({ | |
80 | name => 'index', | |
81 | path => '', | |
82 | method => 'GET', | |
83 | permissions => { user => 'all' }, | |
84 | description => "List available networks", | |
85 | proxyto => 'node', | |
86 | parameters => { | |
87 | additionalProperties => 0, | |
88 | properties => { | |
89 | node => get_standard_option('pve-node'), | |
90 | type => { | |
91 | description => "Only list specific interface types.", | |
92 | type => 'string', | |
93 | enum => ['bond', 'bridge', 'alias', 'eth'], | |
94 | optional => 1, | |
95 | }, | |
96 | }, | |
97 | }, | |
98 | returns => { | |
99 | type => "array", | |
100 | items => { | |
101 | type => "object", | |
102 | properties => {}, | |
103 | }, | |
104 | links => [ { rel => 'child', href => "{iface}" } ], | |
105 | }, | |
106 | code => sub { | |
107 | my ($param) = @_; | |
108 | ||
e4d5bf72 DM |
109 | my $rpcenv = PVE::RPCEnvironment::get(); |
110 | ||
111 | my $tmp = PVE::INotify::read_file('interfaces', 1); | |
112 | my $config = $tmp->{data}; | |
113 | my $changes = $tmp->{changes}; | |
114 | ||
115 | $rpcenv->set_result_attrib('changes', $changes) if $changes; | |
aff192e6 DM |
116 | |
117 | delete $config->{lo}; # do not list the loopback device | |
118 | ||
119 | if ($param->{type}) { | |
120 | foreach my $k (keys %$config) { | |
121 | delete $config->{$k} if $param->{type} ne $config->{$k}->{type}; | |
122 | } | |
123 | } | |
124 | ||
125 | return PVE::RESTHandler::hash_to_array($config, 'iface'); | |
126 | }}); | |
127 | ||
e4d5bf72 DM |
128 | __PACKAGE__->register_method({ |
129 | name => 'revert_network_changes', | |
130 | path => '', | |
131 | method => 'DELETE', | |
132 | permissions => { | |
133 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], | |
134 | }, | |
135 | protected => 1, | |
136 | description => "Revert network configuration changes.", | |
137 | proxyto => 'node', | |
138 | parameters => { | |
139 | additionalProperties => 0, | |
140 | properties => { | |
141 | node => get_standard_option('pve-node'), | |
142 | }, | |
143 | }, | |
144 | returns => { type => "null" }, | |
145 | code => sub { | |
146 | my ($param) = @_; | |
147 | ||
148 | unlink "/etc/network/interfaces.new"; | |
149 | ||
150 | return undef; | |
151 | }}); | |
aff192e6 DM |
152 | |
153 | my $check_duplicate_gateway = sub { | |
154 | my ($config, $newiface) = @_; | |
155 | ||
156 | foreach my $iface (keys %$config) { | |
157 | raise_param_exc({ gateway => "Default gateway already exists on interface '$iface'." }) | |
158 | if ($newiface ne $iface) && $config->{$iface}->{gateway}; | |
159 | } | |
160 | }; | |
161 | ||
162 | ||
163 | __PACKAGE__->register_method({ | |
164 | name => 'create_network', | |
165 | path => '', | |
166 | method => 'POST', | |
167 | permissions => { | |
7d020b42 | 168 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], |
aff192e6 DM |
169 | }, |
170 | description => "Create network device configuration", | |
171 | protected => 1, | |
172 | proxyto => 'node', | |
173 | parameters => { | |
174 | additionalProperties => 0, | |
175 | properties => json_config_properties({ | |
176 | node => get_standard_option('pve-node'), | |
177 | iface => get_standard_option('pve-iface')}), | |
178 | }, | |
179 | returns => { type => 'null' }, | |
180 | code => sub { | |
181 | my ($param) = @_; | |
182 | ||
183 | my $node = extract_param($param, 'node'); | |
184 | my $iface = extract_param($param, 'iface'); | |
185 | ||
186 | my $code = sub { | |
187 | my $config = PVE::INotify::read_file('interfaces'); | |
188 | ||
189 | raise_param_exc({ iface => "interface already exists" }) | |
190 | if $config->{$iface}; | |
191 | ||
192 | &$check_duplicate_gateway($config, $iface) | |
193 | if $param->{gateway}; | |
194 | ||
195 | $param->{method} = $param->{address} ? 'static' : 'manual'; | |
196 | ||
197 | $config->{$iface} = $param; | |
198 | ||
199 | PVE::INotify::write_file('interfaces', $config); | |
200 | }; | |
201 | ||
202 | PVE::Tools::lock_file($iflockfn, 10, $code); | |
203 | die $@ if $@; | |
204 | ||
205 | return undef; | |
206 | }}); | |
207 | ||
208 | __PACKAGE__->register_method({ | |
209 | name => 'update_network', | |
210 | path => '{iface}', | |
211 | method => 'PUT', | |
212 | permissions => { | |
7d020b42 | 213 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], |
aff192e6 DM |
214 | }, |
215 | description => "Update network device configuration", | |
216 | protected => 1, | |
217 | proxyto => 'node', | |
218 | parameters => { | |
219 | additionalProperties => 0, | |
220 | properties => json_config_properties({ | |
221 | node => get_standard_option('pve-node'), | |
222 | iface => get_standard_option('pve-iface'), | |
223 | delete => { | |
224 | type => 'string', format => 'pve-configid-list', | |
225 | description => "A list of settings you want to delete.", | |
226 | optional => 1, | |
227 | }}), | |
228 | }, | |
229 | returns => { type => 'null' }, | |
230 | code => sub { | |
231 | my ($param) = @_; | |
232 | ||
233 | my $node = extract_param($param, 'node'); | |
234 | my $iface = extract_param($param, 'iface'); | |
235 | my $delete = extract_param($param, 'delete'); | |
236 | ||
237 | my $code = sub { | |
238 | my $config = PVE::INotify::read_file('interfaces'); | |
239 | ||
240 | raise_param_exc({ iface => "interface does not exist" }) | |
241 | if !$config->{$iface}; | |
242 | ||
243 | foreach my $k (PVE::Tools::split_list($delete)) { | |
244 | delete $config->{$iface}->{$k}; | |
245 | } | |
246 | ||
247 | &$check_duplicate_gateway($config, $iface) | |
248 | if $param->{gateway}; | |
249 | ||
250 | $param->{method} = $param->{address} ? 'static' : 'manual'; | |
251 | ||
252 | foreach my $k (keys %$param) { | |
253 | $config->{$iface}->{$k} = $param->{$k}; | |
254 | } | |
255 | ||
256 | PVE::INotify::write_file('interfaces', $config); | |
257 | }; | |
258 | ||
259 | PVE::Tools::lock_file($iflockfn, 10, $code); | |
260 | die $@ if $@; | |
261 | ||
262 | return undef; | |
263 | }}); | |
264 | ||
265 | __PACKAGE__->register_method({ | |
266 | name => 'network_config', | |
267 | path => '{iface}', | |
268 | method => 'GET', | |
269 | permissions => { | |
7d020b42 | 270 | check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], |
aff192e6 DM |
271 | }, |
272 | description => "Read network device configuration", | |
273 | proxyto => 'node', | |
274 | parameters => { | |
275 | additionalProperties => 0, | |
276 | properties => { | |
277 | node => get_standard_option('pve-node'), | |
278 | iface => get_standard_option('pve-iface'), | |
279 | }, | |
280 | }, | |
281 | returns => { | |
282 | type => "object", | |
283 | properties => { | |
284 | type => { | |
285 | type => 'string', | |
286 | }, | |
287 | method => { | |
288 | type => 'string', | |
289 | }, | |
290 | }, | |
291 | }, | |
292 | code => sub { | |
293 | my ($param) = @_; | |
294 | ||
295 | my $config = PVE::INotify::read_file('interfaces'); | |
296 | ||
297 | raise_param_exc({ iface => "interface does not exist" }) | |
298 | if !$config->{$param->{iface}}; | |
299 | ||
300 | return $config->{$param->{iface}}; | |
301 | }}); | |
302 | ||
303 | __PACKAGE__->register_method({ | |
304 | name => 'delete_network', | |
305 | path => '{iface}', | |
306 | method => 'DELETE', | |
307 | permissions => { | |
7d020b42 | 308 | check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], |
aff192e6 DM |
309 | }, |
310 | description => "Delete network device configuration", | |
311 | protected => 1, | |
312 | proxyto => 'node', | |
313 | parameters => { | |
314 | additionalProperties => 0, | |
315 | properties => { | |
316 | node => get_standard_option('pve-node'), | |
317 | iface => get_standard_option('pve-iface'), | |
318 | }, | |
319 | }, | |
320 | returns => { type => 'null' }, | |
321 | code => sub { | |
322 | my ($param) = @_; | |
323 | ||
324 | my $code = sub { | |
325 | my $config = PVE::INotify::read_file('interfaces'); | |
326 | ||
327 | raise_param_exc({ iface => "interface does not exist" }) | |
328 | if !$config->{$param->{iface}}; | |
329 | ||
330 | delete $config->{$param->{iface}}; | |
331 | ||
332 | PVE::INotify::write_file('interfaces', $config); | |
333 | }; | |
334 | ||
335 | PVE::Tools::lock_file($iflockfn, 10, $code); | |
336 | die $@ if $@; | |
337 | ||
338 | return undef; | |
339 | }}); |