]> git.proxmox.com Git - pve-ha-manager.git/blob - src/PVE/API2/HA/Resources.pm
Resource/API: abort early if resource in error state
[pve-ha-manager.git] / src / PVE / API2 / HA / Resources.pm
1 package PVE::API2::HA::Resources;
2
3 use strict;
4 use warnings;
5
6 use PVE::SafeSyslog;
7 use PVE::Tools qw(extract_param);
8 use PVE::Cluster;
9 use PVE::HA::Config;
10 use PVE::HA::Resources;
11 use HTTP::Status qw(:constants);
12 use Storable qw(dclone);
13 use PVE::JSONSchema qw(get_standard_option);
14 use PVE::RPCEnvironment;
15 use Data::Dumper;
16
17 use PVE::RESTHandler;
18
19 use base qw(PVE::RESTHandler);
20
21 # fixme: use cfs_read_file
22
23 my $resource_type_enum = PVE::HA::Resources->lookup_types();
24
25 my $api_copy_config = sub {
26 my ($cfg, $sid) = @_;
27
28 die "no such resource '$sid'\n" if !$cfg->{ids}->{$sid};
29
30 my $scfg = dclone($cfg->{ids}->{$sid});
31 $scfg->{sid} = $sid;
32 $scfg->{digest} = $cfg->{digest};
33
34 return $scfg;
35 };
36
37 sub check_service_state {
38 my ($sid, $req_state) = @_;
39
40 my $service_status = PVE::HA::Config::get_service_status($sid);
41 if ($service_status->{managed} && $service_status->{state} eq 'error') {
42 # service in error state, must get disabled before new state request
43 # can be executed
44 return if defined($req_state) && $req_state eq 'disabled';
45 die "service '$sid' in error state, must be disabled and fixed first\n";
46 }
47 }
48
49 __PACKAGE__->register_method ({
50 name => 'index',
51 path => '',
52 method => 'GET',
53 description => "List HA resources.",
54 permissions => {
55 check => ['perm', '/', [ 'Sys.Audit' ]],
56 },
57 parameters => {
58 additionalProperties => 0,
59 properties => {
60 type => {
61 description => "Only list resources of specific type",
62 type => 'string',
63 enum => $resource_type_enum,
64 optional => 1,
65 },
66 },
67 },
68 returns => {
69 type => 'array',
70 items => {
71 type => "object",
72 properties => { sid => { type => 'string'} },
73 },
74 links => [ { rel => 'child', href => "{sid}" } ],
75 },
76 code => sub {
77 my ($param) = @_;
78
79 my $cfg = PVE::HA::Config::read_resources_config();
80 my $groups = PVE::HA::Config::read_group_config();
81
82 my $res = [];
83 foreach my $sid (keys %{$cfg->{ids}}) {
84 my $scfg = &$api_copy_config($cfg, $sid);
85 next if $param->{type} && $param->{type} ne $scfg->{type};
86 if ($scfg->{group} && !$groups->{ids}->{$scfg->{group}}) {
87 $scfg->{errors}->{group} = "group '$scfg->{group}' does not exist";
88 }
89 push @$res, $scfg;
90 }
91
92 return $res;
93 }});
94
95 __PACKAGE__->register_method ({
96 name => 'read',
97 path => '{sid}',
98 method => 'GET',
99 permissions => {
100 check => ['perm', '/', [ 'Sys.Audit' ]],
101 },
102 description => "Read resource configuration.",
103 parameters => {
104 additionalProperties => 0,
105 properties => {
106 sid => get_standard_option('pve-ha-resource-or-vm-id',
107 { completion => \&PVE::HA::Tools::complete_sid }),
108 },
109 },
110 returns => {},
111 code => sub {
112 my ($param) = @_;
113
114 my $cfg = PVE::HA::Config::read_resources_config();
115
116 my $sid = PVE::HA::Tools::parse_sid($param->{sid});
117
118 return &$api_copy_config($cfg, $sid);
119 }});
120
121 __PACKAGE__->register_method ({
122 name => 'create',
123 protected => 1,
124 path => '',
125 method => 'POST',
126 permissions => {
127 check => ['perm', '/', [ 'Sys.Console' ]],
128 },
129 description => "Create a new HA resource.",
130 parameters => PVE::HA::Resources->createSchema(),
131 returns => { type => 'null' },
132 code => sub {
133 my ($param) = @_;
134
135 # create /etc/pve/ha directory
136 PVE::Cluster::check_cfs_quorum();
137 mkdir("/etc/pve/ha");
138
139 my ($sid, $type, $name) = PVE::HA::Tools::parse_sid(extract_param($param, 'sid'));
140
141 if (my $param_type = extract_param($param, 'type')) {
142 # useless, but do it anyway
143 die "types does not match\n" if $param_type ne $type;
144 }
145
146 my $plugin = PVE::HA::Resources->lookup($type);
147 $plugin->verify_name($name);
148
149 $plugin->exists($name);
150
151 my $opts = $plugin->check_config($sid, $param, 1, 1);
152
153 PVE::HA::Config::lock_ha_domain(
154 sub {
155
156 my $cfg = PVE::HA::Config::read_resources_config();
157
158 if ($cfg->{ids}->{$sid}) {
159 die "resource ID '$sid' already defined\n";
160 }
161
162 $cfg->{ids}->{$sid} = $opts;
163
164 PVE::HA::Config::write_resources_config($cfg)
165
166 }, "create resource failed");
167
168 return undef;
169 }});
170
171 __PACKAGE__->register_method ({
172 name => 'update',
173 protected => 1,
174 path => '{sid}',
175 method => 'PUT',
176 description => "Update resource configuration.",
177 permissions => {
178 check => ['perm', '/', [ 'Sys.Console' ]],
179 },
180 parameters => PVE::HA::Resources->updateSchema(),
181 returns => { type => 'null' },
182 code => sub {
183 my ($param) = @_;
184
185 my $digest = extract_param($param, 'digest');
186 my $delete = extract_param($param, 'delete');
187
188 my ($sid, $type, $name) = PVE::HA::Tools::parse_sid(extract_param($param, 'sid'));
189
190 if (my $param_type = extract_param($param, 'type')) {
191 # useless, but do it anyway
192 die "types does not match\n" if $param_type ne $type;
193 }
194
195 if (my $group = $param->{group}) {
196 my $group_cfg = PVE::HA::Config::read_group_config();
197
198 die "HA group '$group' does not exist\n"
199 if !$group_cfg->{ids}->{$group};
200 }
201
202 check_service_state($sid, $param->{state});
203
204 PVE::HA::Config::lock_ha_domain(
205 sub {
206
207 my $cfg = PVE::HA::Config::read_resources_config();
208
209 PVE::SectionConfig::assert_if_modified($cfg, $digest);
210
211 my $scfg = $cfg->{ids}->{$sid} ||
212 die "no such resource '$sid'\n";
213
214 my $plugin = PVE::HA::Resources->lookup($scfg->{type});
215 my $opts = $plugin->check_config($sid, $param, 0, 1);
216
217 foreach my $k (%$opts) {
218 $scfg->{$k} = $opts->{$k};
219 }
220
221 if ($delete) {
222 my $options = $plugin->private()->{options}->{$type};
223 foreach my $k (PVE::Tools::split_list($delete)) {
224 my $d = $options->{$k} ||
225 die "no such option '$k'\n";
226 die "unable to delete required option '$k'\n"
227 if !$d->{optional};
228 die "unable to delete fixed option '$k'\n"
229 if $d->{fixed};
230 delete $scfg->{$k};
231 }
232 }
233
234 PVE::HA::Config::write_resources_config($cfg)
235
236 }, "update resource failed");
237
238 return undef;
239 }});
240
241 __PACKAGE__->register_method ({
242 name => 'delete',
243 protected => 1,
244 path => '{sid}',
245 method => 'DELETE',
246 description => "Delete resource configuration.",
247 permissions => {
248 check => ['perm', '/', [ 'Sys.Console' ]],
249 },
250 parameters => {
251 additionalProperties => 0,
252 properties => {
253 sid => get_standard_option('pve-ha-resource-or-vm-id',
254 { completion => \&PVE::HA::Tools::complete_sid }),
255 },
256 },
257 returns => { type => 'null' },
258 code => sub {
259 my ($param) = @_;
260
261 my ($sid, $type, $name) = PVE::HA::Tools::parse_sid(extract_param($param, 'sid'));
262
263 PVE::HA::Config::service_is_ha_managed($sid);
264
265 PVE::HA::Config::lock_ha_domain(
266 sub {
267
268 my $cfg = PVE::HA::Config::read_resources_config();
269
270 delete $cfg->{ids}->{$sid};
271
272 PVE::HA::Config::write_resources_config($cfg)
273
274 }, "delete resource failed");
275
276 return undef;
277 }});
278
279 __PACKAGE__->register_method ({
280 name => 'migrate',
281 protected => 1,
282 path => '{sid}/migrate',
283 method => 'POST',
284 description => "Request resource migration (online) to another node.",
285 permissions => {
286 check => ['perm', '/', [ 'Sys.Console' ]],
287 },
288 parameters => {
289 additionalProperties => 0,
290 properties => {
291 sid => get_standard_option('pve-ha-resource-or-vm-id',
292 { completion => \&PVE::HA::Tools::complete_sid }),
293 node => get_standard_option('pve-node',
294 { completion => \&PVE::Cluster::get_nodelist }),
295 },
296 },
297 returns => { type => 'null' },
298 code => sub {
299 my ($param) = @_;
300
301 my ($sid, $type, $name) = PVE::HA::Tools::parse_sid(extract_param($param, 'sid'));
302
303 PVE::HA::Config::service_is_ha_managed($sid);
304
305 check_service_state($sid);
306
307 PVE::HA::Config::queue_crm_commands("migrate $sid $param->{node}");
308
309 return undef;
310 }});
311
312 __PACKAGE__->register_method ({
313 name => 'relocate',
314 protected => 1,
315 path => '{sid}/relocate',
316 method => 'POST',
317 description => "Request resource relocatzion to another node. This stops the service on the old node, and restarts it on the target node.",
318 permissions => {
319 check => ['perm', '/', [ 'Sys.Console' ]],
320 },
321 parameters => {
322 additionalProperties => 0,
323 properties => {
324 sid => get_standard_option('pve-ha-resource-or-vm-id',
325 { completion => \&PVE::HA::Tools::complete_sid }),
326 node => get_standard_option('pve-node',
327 { completion => \&PVE::Cluster::get_nodelist }),
328 },
329 },
330 returns => { type => 'null' },
331 code => sub {
332 my ($param) = @_;
333
334 my ($sid, $type, $name) = PVE::HA::Tools::parse_sid(extract_param($param, 'sid'));
335
336 PVE::HA::Config::service_is_ha_managed($sid);
337
338 check_service_state($sid);
339
340 PVE::HA::Config::queue_crm_commands("relocate $sid $param->{node}");
341
342 return undef;
343 }});
344
345 1;