]> git.proxmox.com Git - pve-ha-manager.git/blame - src/PVE/HA/Tools.pm
Tools: add note about indirect include of Config module
[pve-ha-manager.git] / src / PVE / HA / Tools.pm
CommitLineData
54fc75de
DM
1package PVE::HA::Tools;
2
3use strict;
4use warnings;
a223f4cc 5use JSON;
0087839a 6
a223f4cc 7use PVE::JSONSchema;
54fc75de 8use PVE::Tools;
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
54fc75de
DM
87sub read_json_from_file {
88 my ($filename, $default) = @_;
89
90 my $data;
91
92 if (defined($default) && (! -f $filename)) {
93 $data = $default;
94 } else {
6529b6a4 95 my $raw = PVE::Tools::file_get_contents($filename);
54fc75de
DM
96 $data = decode_json($raw);
97 }
98
99 return $data;
100}
101
102sub write_json_to_file {
103 my ($filename, $data) = @_;
104
105 my $raw = encode_json($data);
a223f4cc 106
54fc75de
DM
107 PVE::Tools::file_set_contents($filename, $raw);
108}
109
49777d09
DM
110sub count_fenced_services {
111 my ($ss, $node) = @_;
112
113 my $count = 0;
114
115 foreach my $sid (keys %$ss) {
116 my $sd = $ss->{$sid};
117 next if !$sd->{node};
118 next if $sd->{node} ne $node;
119 my $req_state = $sd->{state};
120 next if !defined($req_state);
121 if ($req_state eq 'fence') {
122 $count++;
123 next;
124 }
125 }
126
127 return $count;
128}
54fc75de 129
aa68337a
TL
130sub get_verbose_service_state {
131 my ($service_state, $service_conf) = @_;
132
dab49a14
TL
133 my $req = $service_conf->{state} // 'ignored';
134 return 'ignored' if $req eq 'ignored';
135
aa68337a
TL
136 # service not yet processed by manager
137 return 'queued' if !defined($service_state);
138 my $cur = $service_state->{state};
139
aa68337a
TL
140 # give fast feedback to the user
141 my $state = $cur;
142 if (!defined($cur)) {
143 $state = 'queued';
144 } elsif ($cur eq 'stopped') {
145 if ($req eq 'started') {
146 $state = 'starting';
147 } elsif ($req eq 'disabled') {
148 $state = 'disabled';
149 }
150 } elsif ($cur eq 'started') {
151 if ($req eq 'stopped' || $req eq 'disabled') {
152 $state = 'stopping';
153 }
154 $state = 'starting' if !$service_state->{running};
155 } elsif ($cur eq 'error') {
156 if ($req eq 'disabled') {
157 $state = 'clearing error flag';
158 }
159 }
160
161 return $state;
162}
163
33783485
TL
164sub upid_wait {
165 my ($upid, $haenv) = @_;
166
167 my $waitfunc = sub {
168 my $task = PVE::Tools::upid_encode(shift);
169 $haenv->log('info', "Task '$task' still active, waiting");
170 };
171
172 PVE::ProcFSTools::upid_wait($upid, $waitfunc, 5);
173}
174
cdf272a0
TL
175# bash auto completion helper
176
1e07d70c
TL
177# NOTE: we use PVE::HA::Config here without declaring an 'use' clause above as
178# an hack. It uses the PVE::Cluster module from pve-cluster, which we do not
179# have nor want as dependency in the simulator - where the completion helpers
180# are never called. The PVE::CLI::ha_manager package pulls it in for us.
181
cdf272a0 182sub complete_sid {
bf119a50 183 my ($cmd, $pname, $cur) = @_;
cdf272a0 184
bf119a50 185 my $cfg = PVE::HA::Config::read_resources_config();
cdf272a0
TL
186
187 my $res = [];
cdf272a0 188
bf119a50 189 if ($cmd eq 'add') {
cdf272a0 190
bf119a50
DM
191 my $vmlist = PVE::Cluster::get_vmlist();
192
193 while (my ($vmid, $info) = each %{$vmlist->{ids}}) {
194
195 my $sid;
196
197 if ($info->{type} eq 'lxc') {
198 $sid = "ct:$vmid";
199 } elsif ($info->{type} eq 'qemu') {
200 $sid = "vm:$vmid";
201 } else {
202 next; # should not happen
203 }
204
205 next if $cfg->{ids}->{$sid};
206
207 push @$res, $sid;
208 }
209
210 } else {
211
212 foreach my $sid (keys %{$cfg->{ids}}) {
213 push @$res, $sid;
cdf272a0 214 }
bf119a50 215 }
cdf272a0 216
bf119a50
DM
217 return $res;
218}
219
220sub complete_enabled_sid {
bf119a50 221 my $cfg = PVE::HA::Config::read_resources_config();
cdf272a0 222
bf119a50
DM
223 my $res = [];
224 foreach my $sid (keys %{$cfg->{ids}}) {
bb07bd2c
TL
225 my $state = $cfg->{ids}->{$sid}->{state} // 'started';
226 next if $state ne 'started';
cdf272a0 227 push @$res, $sid;
bf119a50 228 }
cdf272a0 229
bf119a50
DM
230 return $res;
231}
232
233sub complete_disabled_sid {
bf119a50
DM
234 my $cfg = PVE::HA::Config::read_resources_config();
235
236 my $res = [];
237 foreach my $sid (keys %{$cfg->{ids}}) {
bb07bd2c
TL
238 my $state = $cfg->{ids}->{$sid}->{state} // 'started';
239 next if $state eq 'started';
bf119a50 240 push @$res, $sid;
cdf272a0
TL
241 }
242
243 return $res;
244}
245
246sub complete_group {
bc544941 247 my ($cmd, $pname, $cur) = @_;
cdf272a0
TL
248
249 my $cfg = PVE::HA::Config::read_group_config();
250
251 my $res = [];
bc544941
TL
252 if ($cmd ne 'groupadd') {
253
254 foreach my $group (keys %{$cfg->{ids}}) {
255 push @$res, $group;
256 }
257
cdf272a0
TL
258 }
259
260 return $res;
261}
262
54fc75de 2631;