]>
Commit | Line | Data |
---|---|---|
54fc75de DM |
1 | package PVE::HA::Tools; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
a223f4cc | 5 | use JSON; |
0087839a | 6 | |
a223f4cc | 7 | use PVE::JSONSchema; |
54fc75de | 8 | use PVE::Tools; |
33783485 | 9 | use PVE::ProcFSTools; |
54fc75de | 10 | |
80ec7386 TL |
11 | # return codes used in the ha environment |
12 | # mainly by the resource agents | |
13 | use 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 |
c0dbab3c | 22 | IGNORED => 8, # action was ignored for some good reason |
80ec7386 TL |
23 | }; |
24 | ||
25 | # get constants out of package in a somewhat easy way | |
26 | use base 'Exporter'; | |
27 | our @EXPORT_OK = qw(SUCCESS ERROR EWRONG_NODE EUNKNOWN_SERVICE_TYPE | |
c0dbab3c | 28 | EUNKNOWN_COMMAND EINVALID_PARAMETER ETRY_AGAIN EUNKNOWN_SERVICE IGNORED); |
80ec7386 TL |
29 | our %EXPORT_TAGS = ( 'exit_codes' => [@EXPORT_OK] ); |
30 | ||
95ea5d67 DM |
31 | PVE::JSONSchema::register_format('pve-ha-resource-id', \&pve_verify_ha_resource_id); |
32 | sub pve_verify_ha_resource_id { | |
33 | my ($sid, $noerr) = @_; | |
34 | ||
35 | if ($sid !~ m/^[a-z]+:\S+$/) { | |
36 | return undef if $noerr; | |
37 | die "value does not look like a valid ha resource id\n"; | |
38 | } | |
39 | return $sid; | |
40 | } | |
41 | ||
42 | PVE::JSONSchema::register_standard_option('pve-ha-resource-id', { | |
427e33b6 | 43 | 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 | 44 | typetext => "<type>:<name>", |
289e4784 | 45 | type => 'string', format => 'pve-ha-resource-id', |
95ea5d67 DM |
46 | }); |
47 | ||
6ca2edcd DM |
48 | PVE::JSONSchema::register_format('pve-ha-resource-or-vm-id', \&pve_verify_ha_resource_or_vm_id); |
49 | sub pve_verify_ha_resource_or_vm_id { | |
50 | my ($sid, $noerr) = @_; | |
51 | ||
52 | if ($sid !~ m/^([a-z]+:\S+|\d+)$/) { | |
53 | return undef if $noerr; | |
54 | die "value does not look like a valid ha resource id\n"; | |
55 | } | |
56 | return $sid; | |
57 | } | |
58 | ||
59 | PVE::JSONSchema::register_standard_option('pve-ha-resource-or-vm-id', { | |
427e33b6 | 60 | 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 | 61 | typetext => "<type>:<name>", |
289e4784 | 62 | type => 'string', format => 'pve-ha-resource-or-vm-id', |
6ca2edcd DM |
63 | }); |
64 | ||
a223f4cc DM |
65 | PVE::JSONSchema::register_format('pve-ha-group-node', \&pve_verify_ha_group_node); |
66 | sub pve_verify_ha_group_node { | |
67 | my ($node, $noerr) = @_; | |
68 | ||
69 | if ($node !~ m/^([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)(:\d+)?$/) { | |
70 | return undef if $noerr; | |
71 | die "value does not look like a valid ha group node\n"; | |
72 | } | |
73 | return $node; | |
74 | } | |
75 | ||
76 | PVE::JSONSchema::register_standard_option('pve-ha-group-node-list', { | |
8eac610f DM |
77 | description => "List of cluster node names with optional priority.", |
78 | 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 |
79 | type => 'string', format => 'pve-ha-group-node-list', |
80 | typetext => '<node>[:<pri>]{,<node>[:<pri>]}*', | |
81 | }); | |
82 | ||
83 | PVE::JSONSchema::register_standard_option('pve-ha-group-id', { | |
84 | description => "The HA group identifier.", | |
85 | type => 'string', format => 'pve-configid', | |
86 | }); | |
87 | ||
54fc75de DM |
88 | sub read_json_from_file { |
89 | my ($filename, $default) = @_; | |
90 | ||
91 | my $data; | |
92 | ||
93 | if (defined($default) && (! -f $filename)) { | |
94 | $data = $default; | |
95 | } else { | |
6529b6a4 | 96 | my $raw = PVE::Tools::file_get_contents($filename); |
54fc75de DM |
97 | $data = decode_json($raw); |
98 | } | |
99 | ||
100 | return $data; | |
101 | } | |
102 | ||
103 | sub write_json_to_file { | |
104 | my ($filename, $data) = @_; | |
105 | ||
106 | my $raw = encode_json($data); | |
a223f4cc | 107 | |
54fc75de DM |
108 | PVE::Tools::file_set_contents($filename, $raw); |
109 | } | |
110 | ||
49777d09 DM |
111 | sub count_fenced_services { |
112 | my ($ss, $node) = @_; | |
113 | ||
114 | my $count = 0; | |
289e4784 | 115 | |
49777d09 DM |
116 | foreach my $sid (keys %$ss) { |
117 | my $sd = $ss->{$sid}; | |
118 | next if !$sd->{node}; | |
119 | next if $sd->{node} ne $node; | |
120 | my $req_state = $sd->{state}; | |
121 | next if !defined($req_state); | |
122 | if ($req_state eq 'fence') { | |
123 | $count++; | |
124 | next; | |
125 | } | |
126 | } | |
289e4784 | 127 | |
49777d09 DM |
128 | return $count; |
129 | } | |
54fc75de | 130 | |
aa68337a TL |
131 | sub get_verbose_service_state { |
132 | my ($service_state, $service_conf) = @_; | |
133 | ||
dcb4a2a4 TL |
134 | return 'deleting' if !$service_conf; |
135 | ||
dab49a14 TL |
136 | my $req = $service_conf->{state} // 'ignored'; |
137 | return 'ignored' if $req eq 'ignored'; | |
138 | ||
fbda2658 TL |
139 | return 'not found' if !defined($service_conf->{node}); |
140 | ||
aa68337a TL |
141 | # service not yet processed by manager |
142 | return 'queued' if !defined($service_state); | |
143 | my $cur = $service_state->{state}; | |
144 | ||
aa68337a TL |
145 | # give fast feedback to the user |
146 | my $state = $cur; | |
147 | if (!defined($cur)) { | |
148 | $state = 'queued'; | |
149 | } elsif ($cur eq 'stopped') { | |
150 | if ($req eq 'started') { | |
151 | $state = 'starting'; | |
152 | } elsif ($req eq 'disabled') { | |
153 | $state = 'disabled'; | |
154 | } | |
155 | } elsif ($cur eq 'started') { | |
156 | if ($req eq 'stopped' || $req eq 'disabled') { | |
157 | $state = 'stopping'; | |
158 | } | |
159 | $state = 'starting' if !$service_state->{running}; | |
160 | } elsif ($cur eq 'error') { | |
161 | if ($req eq 'disabled') { | |
162 | $state = 'clearing error flag'; | |
163 | } | |
164 | } | |
165 | ||
166 | return $state; | |
167 | } | |
168 | ||
33783485 TL |
169 | sub upid_wait { |
170 | my ($upid, $haenv) = @_; | |
171 | ||
172 | my $waitfunc = sub { | |
173 | my $task = PVE::Tools::upid_encode(shift); | |
174 | $haenv->log('info', "Task '$task' still active, waiting"); | |
175 | }; | |
176 | ||
177 | PVE::ProcFSTools::upid_wait($upid, $waitfunc, 5); | |
178 | } | |
179 | ||
cdf272a0 TL |
180 | # bash auto completion helper |
181 | ||
1e07d70c TL |
182 | # NOTE: we use PVE::HA::Config here without declaring an 'use' clause above as |
183 | # an hack. It uses the PVE::Cluster module from pve-cluster, which we do not | |
184 | # have nor want as dependency in the simulator - where the completion helpers | |
185 | # are never called. The PVE::CLI::ha_manager package pulls it in for us. | |
186 | ||
cdf272a0 | 187 | sub complete_sid { |
bf119a50 | 188 | my ($cmd, $pname, $cur) = @_; |
cdf272a0 | 189 | |
bf119a50 | 190 | my $cfg = PVE::HA::Config::read_resources_config(); |
cdf272a0 TL |
191 | |
192 | my $res = []; | |
cdf272a0 | 193 | |
bf119a50 | 194 | if ($cmd eq 'add') { |
cdf272a0 | 195 | |
bf119a50 DM |
196 | my $vmlist = PVE::Cluster::get_vmlist(); |
197 | ||
198 | while (my ($vmid, $info) = each %{$vmlist->{ids}}) { | |
199 | ||
200 | my $sid; | |
201 | ||
202 | if ($info->{type} eq 'lxc') { | |
203 | $sid = "ct:$vmid"; | |
204 | } elsif ($info->{type} eq 'qemu') { | |
205 | $sid = "vm:$vmid"; | |
206 | } else { | |
207 | next; # should not happen | |
208 | } | |
209 | ||
210 | next if $cfg->{ids}->{$sid}; | |
211 | ||
212 | push @$res, $sid; | |
213 | } | |
214 | ||
215 | } else { | |
216 | ||
217 | foreach my $sid (keys %{$cfg->{ids}}) { | |
218 | push @$res, $sid; | |
cdf272a0 | 219 | } |
bf119a50 | 220 | } |
cdf272a0 | 221 | |
bf119a50 DM |
222 | return $res; |
223 | } | |
224 | ||
225 | sub complete_enabled_sid { | |
bf119a50 | 226 | my $cfg = PVE::HA::Config::read_resources_config(); |
cdf272a0 | 227 | |
bf119a50 DM |
228 | my $res = []; |
229 | foreach my $sid (keys %{$cfg->{ids}}) { | |
bb07bd2c TL |
230 | my $state = $cfg->{ids}->{$sid}->{state} // 'started'; |
231 | next if $state ne 'started'; | |
cdf272a0 | 232 | push @$res, $sid; |
bf119a50 | 233 | } |
cdf272a0 | 234 | |
bf119a50 DM |
235 | return $res; |
236 | } | |
237 | ||
238 | sub complete_disabled_sid { | |
bf119a50 DM |
239 | my $cfg = PVE::HA::Config::read_resources_config(); |
240 | ||
241 | my $res = []; | |
242 | foreach my $sid (keys %{$cfg->{ids}}) { | |
bb07bd2c TL |
243 | my $state = $cfg->{ids}->{$sid}->{state} // 'started'; |
244 | next if $state eq 'started'; | |
bf119a50 | 245 | push @$res, $sid; |
cdf272a0 TL |
246 | } |
247 | ||
248 | return $res; | |
249 | } | |
250 | ||
251 | sub complete_group { | |
bc544941 | 252 | my ($cmd, $pname, $cur) = @_; |
cdf272a0 TL |
253 | |
254 | my $cfg = PVE::HA::Config::read_group_config(); | |
255 | ||
256 | my $res = []; | |
bc544941 TL |
257 | if ($cmd ne 'groupadd') { |
258 | ||
259 | foreach my $group (keys %{$cfg->{ids}}) { | |
260 | push @$res, $group; | |
261 | } | |
262 | ||
cdf272a0 TL |
263 | } |
264 | ||
265 | return $res; | |
266 | } | |
267 | ||
54fc75de | 268 | 1; |