1 package PVE
::API2
::HA
::Resources
;
7 use PVE
::Tools
qw(extract_param);
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
;
18 use base
qw(PVE::RESTHandler);
20 # fixme: use cfs_read_file
22 my $resource_type_enum = PVE
::HA
::Resources-
>lookup_types();
24 my $api_copy_config = sub {
27 die "no such resource '$sid'\n" if !$cfg->{ids
}->{$sid};
29 my $scfg = dclone
($cfg->{ids
}->{$sid});
31 $scfg->{digest
} = $cfg->{digest
};
36 sub check_service_state
{
37 my ($sid, $req_state) = @_;
39 my $service_status = PVE
::HA
::Config
::get_service_status
($sid);
40 if ($service_status->{managed
} && $service_status->{state} eq 'error') {
41 # service in error state, must get disabled before new state request
43 return if defined($req_state) && $req_state eq 'disabled';
44 die "service '$sid' in error state, must be disabled and fixed first\n";
48 __PACKAGE__-
>register_method ({
52 description
=> "List HA resources.",
54 check
=> ['perm', '/', [ 'Sys.Audit' ]],
57 additionalProperties
=> 0,
60 description
=> "Only list resources of specific type",
62 enum
=> $resource_type_enum,
71 properties
=> { sid
=> { type
=> 'string'} },
73 links
=> [ { rel
=> 'child', href
=> "{sid}" } ],
78 my $cfg = PVE
::HA
::Config
::read_resources_config
();
79 my $groups = PVE
::HA
::Config
::read_group_config
();
82 foreach my $sid (keys %{$cfg->{ids
}}) {
83 my $scfg = &$api_copy_config($cfg, $sid);
84 next if $param->{type
} && $param->{type
} ne $scfg->{type
};
85 if ($scfg->{group
} && !$groups->{ids
}->{$scfg->{group
}}) {
86 $scfg->{errors
}->{group
} = "group '$scfg->{group}' does not exist";
94 __PACKAGE__-
>register_method ({
99 check
=> ['perm', '/', [ 'Sys.Audit' ]],
101 description
=> "Read resource configuration.",
103 additionalProperties
=> 0,
105 sid
=> get_standard_option
('pve-ha-resource-or-vm-id',
106 { completion
=> \
&PVE
::HA
::Tools
::complete_sid
}),
112 sid
=> get_standard_option
('pve-ha-resource-or-vm-id'),
115 description
=> 'Can be used to prevent concurrent modifications.',
119 description
=> 'The type of the resources.',
123 enum
=> ['started', 'stopped', 'enabled', 'disabled', 'ignored'],
125 description
=> "Requested resource state.",
127 group
=> get_standard_option
('pve-ha-group-id', { optional
=> 1, }),
129 description
=> "Maximal number of tries to restart the service on".
130 " a node after its start failed.",
135 description
=> "Maximal number of service relocate tries when a".
136 " service failes to start.",
141 description
=> "Description.",
150 my $cfg = PVE
::HA
::Config
::read_resources_config
();
152 my $sid = PVE
::HA
::Config
::parse_sid
($param->{sid
});
154 return &$api_copy_config($cfg, $sid);
157 __PACKAGE__-
>register_method ({
163 check
=> ['perm', '/', [ 'Sys.Console' ]],
165 description
=> "Create a new HA resource.",
166 parameters
=> PVE
::HA
::Resources-
>createSchema(),
167 returns
=> { type
=> 'null' },
171 # create /etc/pve/ha directory
172 PVE
::Cluster
::check_cfs_quorum
();
173 mkdir("/etc/pve/ha");
175 my ($sid, $type, $name) = PVE
::HA
::Config
::parse_sid
(extract_param
($param, 'sid'));
177 if (my $param_type = extract_param
($param, 'type')) {
178 # useless, but do it anyway
179 die "types does not match\n" if $param_type ne $type;
182 my $plugin = PVE
::HA
::Resources-
>lookup($type);
183 $plugin->verify_name($name);
185 $plugin->exists($name);
187 my $opts = $plugin->check_config($sid, $param, 1, 1);
189 PVE
::HA
::Config
::lock_ha_domain
(
192 my $cfg = PVE
::HA
::Config
::read_resources_config
();
194 if ($cfg->{ids
}->{$sid}) {
195 die "resource ID '$sid' already defined\n";
198 $cfg->{ids
}->{$sid} = $opts;
200 PVE
::HA
::Config
::write_resources_config
($cfg)
202 }, "create resource failed");
207 __PACKAGE__-
>register_method ({
212 description
=> "Update resource configuration.",
214 check
=> ['perm', '/', [ 'Sys.Console' ]],
216 parameters
=> PVE
::HA
::Resources-
>updateSchema(),
217 returns
=> { type
=> 'null' },
221 my $digest = extract_param
($param, 'digest');
222 my $delete = extract_param
($param, 'delete');
224 my ($sid, $type, $name) = PVE
::HA
::Config
::parse_sid
(extract_param
($param, 'sid'));
226 if (my $param_type = extract_param
($param, 'type')) {
227 # useless, but do it anyway
228 die "types does not match\n" if $param_type ne $type;
231 if (my $group = $param->{group
}) {
232 my $group_cfg = PVE
::HA
::Config
::read_group_config
();
234 die "HA group '$group' does not exist\n"
235 if !$group_cfg->{ids
}->{$group};
238 check_service_state
($sid, $param->{state});
240 PVE
::HA
::Config
::lock_ha_domain
(
243 my $cfg = PVE
::HA
::Config
::read_resources_config
();
245 PVE
::SectionConfig
::assert_if_modified
($cfg, $digest);
247 my $scfg = $cfg->{ids
}->{$sid} ||
248 die "no such resource '$sid'\n";
250 my $plugin = PVE
::HA
::Resources-
>lookup($scfg->{type
});
251 my $opts = $plugin->check_config($sid, $param, 0, 1);
253 foreach my $k (%$opts) {
254 $scfg->{$k} = $opts->{$k};
258 my $options = $plugin->private()->{options
}->{$type};
259 foreach my $k (PVE
::Tools
::split_list
($delete)) {
260 my $d = $options->{$k} ||
261 die "no such option '$k'\n";
262 die "unable to delete required option '$k'\n"
264 die "unable to delete fixed option '$k'\n"
270 PVE
::HA
::Config
::write_resources_config
($cfg)
272 }, "update resource failed");
277 __PACKAGE__-
>register_method ({
282 description
=> "Delete resource configuration.",
284 check
=> ['perm', '/', [ 'Sys.Console' ]],
287 additionalProperties
=> 0,
289 sid
=> get_standard_option
('pve-ha-resource-or-vm-id',
290 { completion
=> \
&PVE
::HA
::Tools
::complete_sid
}),
293 returns
=> { type
=> 'null' },
297 my ($sid, $type, $name) = PVE
::HA
::Config
::parse_sid
(extract_param
($param, 'sid'));
299 my $cfg = PVE
::HA
::Config
::read_resources_config
();
301 # cannot use service_is_ha_managed as it ignores 'ignored' services,
302 # see bug report #1602
303 if (!defined($cfg->{ids
}) || !defined($cfg->{ids
}->{$sid})) {
304 die "cannot delete service '$sid', not HA managed!\n";
307 PVE
::HA
::Config
::lock_ha_domain
(sub {
309 $cfg = PVE
::HA
::Config
::read_resources_config
();
310 delete $cfg->{ids
}->{$sid} or die "'$sid' not configured!\n";
311 PVE
::HA
::Config
::write_resources_config
($cfg);
313 }, "delete resource failed");
318 __PACKAGE__-
>register_method ({
321 path
=> '{sid}/migrate',
323 description
=> "Request resource migration (online) to another node.",
325 check
=> ['perm', '/', [ 'Sys.Console' ]],
328 additionalProperties
=> 0,
330 sid
=> get_standard_option
('pve-ha-resource-or-vm-id',
331 { completion
=> \
&PVE
::HA
::Tools
::complete_sid
}),
332 node
=> get_standard_option
('pve-node', {
333 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
334 description
=> "Target node.",
338 returns
=> { type
=> 'null' },
342 my ($sid, $type, $name) = PVE
::HA
::Config
::parse_sid
(extract_param
($param, 'sid'));
344 PVE
::HA
::Config
::service_is_ha_managed
($sid);
346 check_service_state
($sid);
348 PVE
::HA
::Config
::queue_crm_commands
("migrate $sid $param->{node}");
353 __PACKAGE__-
>register_method ({
356 path
=> '{sid}/relocate',
358 description
=> "Request resource relocatzion to another node. This stops the service on the old node, and restarts it on the target node.",
360 check
=> ['perm', '/', [ 'Sys.Console' ]],
363 additionalProperties
=> 0,
365 sid
=> get_standard_option
('pve-ha-resource-or-vm-id',
366 { completion
=> \
&PVE
::HA
::Tools
::complete_sid
}),
367 node
=> get_standard_option
('pve-node', {
368 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
369 description
=> "Target node.",
373 returns
=> { type
=> 'null' },
377 my ($sid, $type, $name) = PVE
::HA
::Config
::parse_sid
(extract_param
($param, 'sid'));
379 PVE
::HA
::Config
::service_is_ha_managed
($sid);
381 check_service_state
($sid);
383 PVE
::HA
::Config
::queue_crm_commands
("relocate $sid $param->{node}");