]> git.proxmox.com Git - pve-ha-manager.git/blob - src/PVE/HA/Tools.pm
dca7dafb31c73a40e6e06cd7ca3008d71eb1116a
[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 use PVE::JSONSchema;
7 use PVE::Tools;
8 use PVE::Cluster;
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 parse_sid {
88 my ($sid) = @_;
89
90 my ($type, $name);
91
92 if ($sid =~ m/^(\d+)$/) {
93 $name = $1;
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 }
109 } elsif ($sid =~m/^(\S+):(\S+)$/) {
110 $name = $2;
111 $type = $1;
112 } else {
113 die "unable to parse service id '$sid'\n";
114 }
115
116 return wantarray ? ($sid, $type, $name) : $sid;
117 }
118
119 sub read_json_from_file {
120 my ($filename, $default) = @_;
121
122 my $data;
123
124 if (defined($default) && (! -f $filename)) {
125 $data = $default;
126 } else {
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 }
137 $data = decode_json($raw);
138 }
139
140 return $data;
141 }
142
143 sub write_json_to_file {
144 my ($filename, $data) = @_;
145
146 my $raw = encode_json($data);
147
148 PVE::Tools::file_set_contents($filename, $raw);
149 }
150
151 sub has_services {
152 my ($haenv, $node) = @_;
153
154 my $conf = $haenv->read_service_config();
155
156 # if no node defined any service count is fine
157 return scalar(%{$conf}) if !defined($node);
158
159 foreach my $d (values %$conf) {
160 return 1 if $d->{node} eq $node;
161 }
162
163 return undef;
164 }
165
166 sub count_fenced_services {
167 my ($ss, $node) = @_;
168
169 my $count = 0;
170
171 foreach my $sid (keys %$ss) {
172 my $sd = $ss->{$sid};
173 next if !$sd->{node};
174 next if $sd->{node} ne $node;
175 my $req_state = $sd->{state};
176 next if !defined($req_state);
177 if ($req_state eq 'fence') {
178 $count++;
179 next;
180 }
181 }
182
183 return $count;
184 }
185
186 sub upid_wait {
187 my ($upid, $haenv) = @_;
188
189 my $waitfunc = sub {
190 my $task = PVE::Tools::upid_encode(shift);
191 $haenv->log('info', "Task '$task' still active, waiting");
192 };
193
194 PVE::ProcFSTools::upid_wait($upid, $waitfunc, 5);
195 }
196
197 # bash auto completion helper
198
199 sub complete_sid {
200 my ($cmd, $pname, $cur) = @_;
201
202 my $cfg = PVE::HA::Config::read_resources_config();
203
204 my $res = [];
205
206 if ($cmd eq 'add') {
207
208 my $vmlist = PVE::Cluster::get_vmlist();
209
210 while (my ($vmid, $info) = each %{$vmlist->{ids}}) {
211
212 my $sid;
213
214 if ($info->{type} eq 'lxc') {
215 $sid = "ct:$vmid";
216 } elsif ($info->{type} eq 'qemu') {
217 $sid = "vm:$vmid";
218 } else {
219 next; # should not happen
220 }
221
222 next if $cfg->{ids}->{$sid};
223
224 push @$res, $sid;
225 }
226
227 } else {
228
229 foreach my $sid (keys %{$cfg->{ids}}) {
230 push @$res, $sid;
231 }
232 }
233
234 return $res;
235 }
236
237 sub complete_enabled_sid {
238
239 my $cfg = PVE::HA::Config::read_resources_config();
240
241 my $res = [];
242 foreach my $sid (keys %{$cfg->{ids}}) {
243 my $state = $cfg->{ids}->{$sid}->{state} // 'started';
244 next if $state ne 'started';
245 push @$res, $sid;
246 }
247
248 return $res;
249 }
250
251 sub complete_disabled_sid {
252
253 my $cfg = PVE::HA::Config::read_resources_config();
254
255 my $res = [];
256 foreach my $sid (keys %{$cfg->{ids}}) {
257 my $state = $cfg->{ids}->{$sid}->{state} // 'started';
258 next if $state eq 'started';
259 push @$res, $sid;
260 }
261
262 return $res;
263 }
264
265 sub complete_group {
266 my ($cmd, $pname, $cur) = @_;
267
268 my $cfg = PVE::HA::Config::read_group_config();
269
270 my $res = [];
271 if ($cmd ne 'groupadd') {
272
273 foreach my $group (keys %{$cfg->{ids}}) {
274 push @$res, $group;
275 }
276
277 }
278
279 return $res;
280 }
281
282
283 1;