]> git.proxmox.com Git - pve-ha-manager.git/blame - src/PVE/HA/Tools.pm
CRM: refactor check if state transition to active is ok
[pve-ha-manager.git] / src / PVE / HA / Tools.pm
CommitLineData
54fc75de
DM
1package PVE::HA::Tools;
2
3use strict;
4use warnings;
a223f4cc
DM
5use JSON;
6use PVE::JSONSchema;
54fc75de 7use PVE::Tools;
427e33b6 8use PVE::Cluster;
33783485 9use PVE::ProcFSTools;
54fc75de 10
80ec7386
TL
11# return codes used in the ha environment
12# mainly by the resource agents
13use constant {
14 SUCCESS => 0, # action finished as expected
15 ERROR => 1, # action was erroneous
16 ETRY_AGAIN => 2, # action was erroneous and needs to be repeated
17 EWRONG_NODE => 3, # needs to fixup the service location
18 EUNKNOWN_SERVICE_TYPE => 4, # no plugin for this type service found
19 EUNKNOWN_COMMAND => 5,
20 EINVALID_PARAMETER => 6,
aaabde6a 21 EUNKNOWN_SERVICE => 7, # service not found
80ec7386
TL
22};
23
24# get constants out of package in a somewhat easy way
25use base 'Exporter';
26our @EXPORT_OK = qw(SUCCESS ERROR EWRONG_NODE EUNKNOWN_SERVICE_TYPE
aaabde6a 27 EUNKNOWN_COMMAND EINVALID_PARAMETER ETRY_AGAIN EUNKNOWN_SERVICE);
80ec7386
TL
28our %EXPORT_TAGS = ( 'exit_codes' => [@EXPORT_OK] );
29
95ea5d67
DM
30PVE::JSONSchema::register_format('pve-ha-resource-id', \&pve_verify_ha_resource_id);
31sub pve_verify_ha_resource_id {
32 my ($sid, $noerr) = @_;
33
34 if ($sid !~ m/^[a-z]+:\S+$/) {
35 return undef if $noerr;
36 die "value does not look like a valid ha resource id\n";
37 }
38 return $sid;
39}
40
41PVE::JSONSchema::register_standard_option('pve-ha-resource-id', {
427e33b6 42 description => "HA resource ID. This consists of a resource type followed by a resource specific name, separated with colon (example: vm:100 / ct:100).",
95ea5d67
DM
43 typetext => "<type>:<name>",
44 type => 'string', format => 'pve-ha-resource-id',
45});
46
6ca2edcd
DM
47PVE::JSONSchema::register_format('pve-ha-resource-or-vm-id', \&pve_verify_ha_resource_or_vm_id);
48sub pve_verify_ha_resource_or_vm_id {
49 my ($sid, $noerr) = @_;
50
51 if ($sid !~ m/^([a-z]+:\S+|\d+)$/) {
52 return undef if $noerr;
53 die "value does not look like a valid ha resource id\n";
54 }
55 return $sid;
56}
57
58PVE::JSONSchema::register_standard_option('pve-ha-resource-or-vm-id', {
427e33b6 59 description => "HA resource ID. This consists of a resource type followed by a resource specific name, separated with colon (example: vm:100 / ct:100). For virtual machines and containers, you can simply use the VM or CT id as a shortcut (example: 100).",
6ca2edcd
DM
60 typetext => "<type>:<name>",
61 type => 'string', format => 'pve-ha-resource-or-vm-id',
62});
63
a223f4cc
DM
64PVE::JSONSchema::register_format('pve-ha-group-node', \&pve_verify_ha_group_node);
65sub pve_verify_ha_group_node {
66 my ($node, $noerr) = @_;
67
68 if ($node !~ m/^([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)(:\d+)?$/) {
69 return undef if $noerr;
70 die "value does not look like a valid ha group node\n";
71 }
72 return $node;
73}
74
75PVE::JSONSchema::register_standard_option('pve-ha-group-node-list', {
8eac610f
DM
76 description => "List of cluster node names with optional priority.",
77 verbose_description => "List of cluster node members, where a priority can be given to each node. A resource bound to a group will run on the available nodes with the highest priority. If there are more nodes in the highest priority class, the services will get distributed to those nodes. The priorities have a relative meaning only.",
a223f4cc
DM
78 type => 'string', format => 'pve-ha-group-node-list',
79 typetext => '<node>[:<pri>]{,<node>[:<pri>]}*',
80});
81
82PVE::JSONSchema::register_standard_option('pve-ha-group-id', {
83 description => "The HA group identifier.",
84 type => 'string', format => 'pve-configid',
85});
86
b47a7a1b
DM
87sub parse_sid {
88 my ($sid) = @_;
89
6ca2edcd
DM
90 my ($type, $name);
91
92 if ($sid =~ m/^(\d+)$/) {
93 $name = $1;
427e33b6
AG
94 my $vmlist = PVE::Cluster::get_vmlist();
95 if (defined($vmlist->{ids}->{$name})) {
96 my $vm_type = $vmlist->{ids}->{$name}->{type};
97 if ($vm_type eq 'lxc') {
98 $type = 'ct';
99 } elsif ($vm_type eq 'qemu') {
100 $type = 'vm';
101 } else {
102 die "internal error";
103 }
104 $sid = "$type:$name";
105 }
106 else {
107 die "unable do add resource - VM/CT $1 does not exist\n";
108 }
6ca2edcd
DM
109 } elsif ($sid =~m/^(\S+):(\S+)$/) {
110 $name = $2;
111 $type = $1;
112 } else {
113 die "unable to parse service id '$sid'\n";
114 }
b47a7a1b 115
6ca2edcd 116 return wantarray ? ($sid, $type, $name) : $sid;
b47a7a1b
DM
117}
118
54fc75de
DM
119sub read_json_from_file {
120 my ($filename, $default) = @_;
121
122 my $data;
123
124 if (defined($default) && (! -f $filename)) {
125 $data = $default;
126 } else {
3b8cba06
DM
127 my $raw;
128 # workaround for bug #775
129 if ($filename =~ m|^/etc/pve/|) {
130 $filename =~ s|^/etc/pve/+||;
131 $raw = PVE::Cluster::get_config($filename);
132 die "unable to read file '/etc/pve/$filename'\n"
133 if !defined($raw);
134 } else {
135 $raw = PVE::Tools::file_get_contents($filename);
136 }
54fc75de
DM
137 $data = decode_json($raw);
138 }
139
140 return $data;
141}
142
143sub write_json_to_file {
144 my ($filename, $data) = @_;
145
146 my $raw = encode_json($data);
a223f4cc 147
54fc75de
DM
148 PVE::Tools::file_set_contents($filename, $raw);
149}
150
49777d09
DM
151sub count_fenced_services {
152 my ($ss, $node) = @_;
153
154 my $count = 0;
155
156 foreach my $sid (keys %$ss) {
157 my $sd = $ss->{$sid};
158 next if !$sd->{node};
159 next if $sd->{node} ne $node;
160 my $req_state = $sd->{state};
161 next if !defined($req_state);
162 if ($req_state eq 'fence') {
163 $count++;
164 next;
165 }
166 }
167
168 return $count;
169}
54fc75de 170
aa68337a
TL
171sub get_verbose_service_state {
172 my ($service_state, $service_conf) = @_;
173
dab49a14
TL
174 my $req = $service_conf->{state} // 'ignored';
175 return 'ignored' if $req eq 'ignored';
176
aa68337a
TL
177 # service not yet processed by manager
178 return 'queued' if !defined($service_state);
179 my $cur = $service_state->{state};
180
aa68337a
TL
181 # give fast feedback to the user
182 my $state = $cur;
183 if (!defined($cur)) {
184 $state = 'queued';
185 } elsif ($cur eq 'stopped') {
186 if ($req eq 'started') {
187 $state = 'starting';
188 } elsif ($req eq 'disabled') {
189 $state = 'disabled';
190 }
191 } elsif ($cur eq 'started') {
192 if ($req eq 'stopped' || $req eq 'disabled') {
193 $state = 'stopping';
194 }
195 $state = 'starting' if !$service_state->{running};
196 } elsif ($cur eq 'error') {
197 if ($req eq 'disabled') {
198 $state = 'clearing error flag';
199 }
200 }
201
202 return $state;
203}
204
33783485
TL
205sub upid_wait {
206 my ($upid, $haenv) = @_;
207
208 my $waitfunc = sub {
209 my $task = PVE::Tools::upid_encode(shift);
210 $haenv->log('info', "Task '$task' still active, waiting");
211 };
212
213 PVE::ProcFSTools::upid_wait($upid, $waitfunc, 5);
214}
215
cdf272a0
TL
216# bash auto completion helper
217
218sub complete_sid {
bf119a50 219 my ($cmd, $pname, $cur) = @_;
cdf272a0 220
bf119a50 221 my $cfg = PVE::HA::Config::read_resources_config();
cdf272a0
TL
222
223 my $res = [];
cdf272a0 224
bf119a50 225 if ($cmd eq 'add') {
cdf272a0 226
bf119a50
DM
227 my $vmlist = PVE::Cluster::get_vmlist();
228
229 while (my ($vmid, $info) = each %{$vmlist->{ids}}) {
230
231 my $sid;
232
233 if ($info->{type} eq 'lxc') {
234 $sid = "ct:$vmid";
235 } elsif ($info->{type} eq 'qemu') {
236 $sid = "vm:$vmid";
237 } else {
238 next; # should not happen
239 }
240
241 next if $cfg->{ids}->{$sid};
242
243 push @$res, $sid;
244 }
245
246 } else {
247
248 foreach my $sid (keys %{$cfg->{ids}}) {
249 push @$res, $sid;
cdf272a0 250 }
bf119a50 251 }
cdf272a0 252
bf119a50
DM
253 return $res;
254}
255
256sub complete_enabled_sid {
257
258 my $cfg = PVE::HA::Config::read_resources_config();
cdf272a0 259
bf119a50
DM
260 my $res = [];
261 foreach my $sid (keys %{$cfg->{ids}}) {
bb07bd2c
TL
262 my $state = $cfg->{ids}->{$sid}->{state} // 'started';
263 next if $state ne 'started';
cdf272a0 264 push @$res, $sid;
bf119a50 265 }
cdf272a0 266
bf119a50
DM
267 return $res;
268}
269
270sub complete_disabled_sid {
271
272 my $cfg = PVE::HA::Config::read_resources_config();
273
274 my $res = [];
275 foreach my $sid (keys %{$cfg->{ids}}) {
bb07bd2c
TL
276 my $state = $cfg->{ids}->{$sid}->{state} // 'started';
277 next if $state eq 'started';
bf119a50 278 push @$res, $sid;
cdf272a0
TL
279 }
280
281 return $res;
282}
283
284sub complete_group {
bc544941 285 my ($cmd, $pname, $cur) = @_;
cdf272a0
TL
286
287 my $cfg = PVE::HA::Config::read_group_config();
288
289 my $res = [];
bc544941
TL
290 if ($cmd ne 'groupadd') {
291
292 foreach my $group (keys %{$cfg->{ids}}) {
293 push @$res, $group;
294 }
295
cdf272a0
TL
296 }
297
298 return $res;
299}
300
301
54fc75de 3021;