]> git.proxmox.com Git - pve-ha-manager.git/blob - src/PVE/HA/Tools.pm
1a88351adef145f547df621fef9a135babb0e2ff
[pve-ha-manager.git] / src / PVE / HA / Tools.pm
1 package PVE::HA::Tools;
2
3 use strict;
4 use warnings;
5 use JSON;
6
7 use PVE::JSONSchema;
8 use PVE::Tools;
9 use PVE::ProcFSTools;
10
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,
21 EUNKNOWN_SERVICE => 7, # service not found
22 };
23
24 # get constants out of package in a somewhat easy way
25 use base 'Exporter';
26 our @EXPORT_OK = qw(SUCCESS ERROR EWRONG_NODE EUNKNOWN_SERVICE_TYPE
27 EUNKNOWN_COMMAND EINVALID_PARAMETER ETRY_AGAIN EUNKNOWN_SERVICE);
28 our %EXPORT_TAGS = ( 'exit_codes' => [@EXPORT_OK] );
29
30 PVE::JSONSchema::register_format('pve-ha-resource-id', \&pve_verify_ha_resource_id);
31 sub 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
41 PVE::JSONSchema::register_standard_option('pve-ha-resource-id', {
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).",
43 typetext => "<type>:<name>",
44 type => 'string', format => 'pve-ha-resource-id',
45 });
46
47 PVE::JSONSchema::register_format('pve-ha-resource-or-vm-id', \&pve_verify_ha_resource_or_vm_id);
48 sub 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
58 PVE::JSONSchema::register_standard_option('pve-ha-resource-or-vm-id', {
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).",
60 typetext => "<type>:<name>",
61 type => 'string', format => 'pve-ha-resource-or-vm-id',
62 });
63
64 PVE::JSONSchema::register_format('pve-ha-group-node', \&pve_verify_ha_group_node);
65 sub 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
75 PVE::JSONSchema::register_standard_option('pve-ha-group-node-list', {
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.",
78 type => 'string', format => 'pve-ha-group-node-list',
79 typetext => '<node>[:<pri>]{,<node>[:<pri>]}*',
80 });
81
82 PVE::JSONSchema::register_standard_option('pve-ha-group-id', {
83 description => "The HA group identifier.",
84 type => 'string', format => 'pve-configid',
85 });
86
87 sub read_json_from_file {
88 my ($filename, $default) = @_;
89
90 my $data;
91
92 if (defined($default) && (! -f $filename)) {
93 $data = $default;
94 } else {
95 my $raw = PVE::Tools::file_get_contents($filename);
96 $data = decode_json($raw);
97 }
98
99 return $data;
100 }
101
102 sub write_json_to_file {
103 my ($filename, $data) = @_;
104
105 my $raw = encode_json($data);
106
107 PVE::Tools::file_set_contents($filename, $raw);
108 }
109
110 sub 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 }
129
130 sub get_verbose_service_state {
131 my ($service_state, $service_conf) = @_;
132
133 return 'deleting' if !$service_conf;
134
135 my $req = $service_conf->{state} // 'ignored';
136 return 'ignored' if $req eq 'ignored';
137
138 return 'not found' if !defined($service_conf->{node});
139
140 # service not yet processed by manager
141 return 'queued' if !defined($service_state);
142 my $cur = $service_state->{state};
143
144 # give fast feedback to the user
145 my $state = $cur;
146 if (!defined($cur)) {
147 $state = 'queued';
148 } elsif ($cur eq 'stopped') {
149 if ($req eq 'started') {
150 $state = 'starting';
151 } elsif ($req eq 'disabled') {
152 $state = 'disabled';
153 }
154 } elsif ($cur eq 'started') {
155 if ($req eq 'stopped' || $req eq 'disabled') {
156 $state = 'stopping';
157 }
158 $state = 'starting' if !$service_state->{running};
159 } elsif ($cur eq 'error') {
160 if ($req eq 'disabled') {
161 $state = 'clearing error flag';
162 }
163 }
164
165 return $state;
166 }
167
168 sub upid_wait {
169 my ($upid, $haenv) = @_;
170
171 my $waitfunc = sub {
172 my $task = PVE::Tools::upid_encode(shift);
173 $haenv->log('info', "Task '$task' still active, waiting");
174 };
175
176 PVE::ProcFSTools::upid_wait($upid, $waitfunc, 5);
177 }
178
179 # bash auto completion helper
180
181 # NOTE: we use PVE::HA::Config here without declaring an 'use' clause above as
182 # an hack. It uses the PVE::Cluster module from pve-cluster, which we do not
183 # have nor want as dependency in the simulator - where the completion helpers
184 # are never called. The PVE::CLI::ha_manager package pulls it in for us.
185
186 sub complete_sid {
187 my ($cmd, $pname, $cur) = @_;
188
189 my $cfg = PVE::HA::Config::read_resources_config();
190
191 my $res = [];
192
193 if ($cmd eq 'add') {
194
195 my $vmlist = PVE::Cluster::get_vmlist();
196
197 while (my ($vmid, $info) = each %{$vmlist->{ids}}) {
198
199 my $sid;
200
201 if ($info->{type} eq 'lxc') {
202 $sid = "ct:$vmid";
203 } elsif ($info->{type} eq 'qemu') {
204 $sid = "vm:$vmid";
205 } else {
206 next; # should not happen
207 }
208
209 next if $cfg->{ids}->{$sid};
210
211 push @$res, $sid;
212 }
213
214 } else {
215
216 foreach my $sid (keys %{$cfg->{ids}}) {
217 push @$res, $sid;
218 }
219 }
220
221 return $res;
222 }
223
224 sub complete_enabled_sid {
225 my $cfg = PVE::HA::Config::read_resources_config();
226
227 my $res = [];
228 foreach my $sid (keys %{$cfg->{ids}}) {
229 my $state = $cfg->{ids}->{$sid}->{state} // 'started';
230 next if $state ne 'started';
231 push @$res, $sid;
232 }
233
234 return $res;
235 }
236
237 sub complete_disabled_sid {
238 my $cfg = PVE::HA::Config::read_resources_config();
239
240 my $res = [];
241 foreach my $sid (keys %{$cfg->{ids}}) {
242 my $state = $cfg->{ids}->{$sid}->{state} // 'started';
243 next if $state eq 'started';
244 push @$res, $sid;
245 }
246
247 return $res;
248 }
249
250 sub complete_group {
251 my ($cmd, $pname, $cur) = @_;
252
253 my $cfg = PVE::HA::Config::read_group_config();
254
255 my $res = [];
256 if ($cmd ne 'groupadd') {
257
258 foreach my $group (keys %{$cfg->{ids}}) {
259 push @$res, $group;
260 }
261
262 }
263
264 return $res;
265 }
266
267 1;