]> git.proxmox.com Git - pve-ha-manager.git/blob - src/PVE/HA/Resources.pm
resource agents: fix relocate
[pve-ha-manager.git] / src / PVE / HA / Resources.pm
1 package PVE::HA::Resources;
2
3 use strict;
4 use warnings;
5
6 use Data::Dumper;
7 use PVE::JSONSchema qw(get_standard_option);
8 use PVE::SectionConfig;
9 use PVE::HA::Tools;
10
11 use base qw(PVE::SectionConfig);
12
13 my $defaultData = {
14 propertyList => {
15 type => { description => "Resource type.", optional => 1 },
16 sid => get_standard_option('pve-ha-resource-or-vm-id',
17 { completion => \&PVE::HA::Tools::complete_sid }),
18 state => {
19 description => "Resource state.",
20 type => 'string',
21 enum => ['enabled', 'disabled'],
22 optional => 1,
23 default => 'enabled',
24 },
25 group => get_standard_option('pve-ha-group-id',
26 { optional => 1,
27 completion => \&PVE::HA::Tools::complete_group }),
28 max_restart => {
29 description => "Maximal number of tries to restart the service on".
30 " a node after its start failed.",
31 type => 'integer',
32 optional => 1,
33 default => 1,
34 minimum => 0,
35 },
36 max_relocate => {
37 description => "Maximal number of service relocate tries when a".
38 " service failes to start.",
39 type => 'integer',
40 optional => 1,
41 default => 1,
42 minimum => 0,
43 },
44 comment => {
45 description => "Description.",
46 type => 'string',
47 optional => 1,
48 maxLength => 4096,
49 },
50 },
51 };
52
53 sub verify_name {
54 my ($class, $name) = @_;
55
56 die "implement this in subclass";
57 }
58
59 sub private {
60 return $defaultData;
61 }
62
63 sub format_section_header {
64 my ($class, $type, $sectionId) = @_;
65
66 my (undef, $name) = split(':', $sectionId, 2);
67
68 return "$type: $name\n";
69 }
70
71 sub parse_section_header {
72 my ($class, $line) = @_;
73
74 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
75 my ($type, $name) = (lc($1), $2);
76 my $errmsg = undef; # set if you want to skip whole section
77 eval {
78 if (my $plugin = $defaultData->{plugins}->{$type}) {
79 $plugin->verify_name($name);
80 } else {
81 die "no such resource type '$type'\n";
82 }
83 };
84 $errmsg = $@ if $@;
85 my $config = {}; # to return additional attributes
86 return ($type, "$type:$name", $errmsg, $config);
87 }
88 return undef;
89 }
90
91 sub start {
92 my ($class, $haenv, $id) = @_;
93
94 die "implement in subclass";
95 }
96
97 sub shutdown {
98 my ($class, $haenv, $id) = @_;
99
100 die "implement in subclass";
101 }
102
103 sub migrate {
104 my ($class, $haenv, $id, $target, $online) = @_;
105
106 die "implement in subclass";
107 }
108
109 sub config_file {
110 my ($class, $id, $nodename) = @_;
111
112 die "implement in subclass"
113 }
114
115 sub exists {
116 my ($class, $id, $noerr) = @_;
117
118 die "implement in subclass"
119 }
120
121 sub check_running {
122 my ($class, $id) = @_;
123
124 die "implement in subclass";
125 }
126
127
128 # virtual machine resource class
129 package PVE::HA::Resources::PVEVM;
130
131 use strict;
132 use warnings;
133
134 use PVE::QemuServer;
135 use PVE::API2::Qemu;
136
137 use base qw(PVE::HA::Resources);
138
139 sub type {
140 return 'vm';
141 }
142
143 sub verify_name {
144 my ($class, $name) = @_;
145
146 die "invalid VMID\n" if $name !~ m/^[1-9][0-9]+$/;
147 }
148
149 sub options {
150 return {
151 state => { optional => 1 },
152 group => { optional => 1 },
153 comment => { optional => 1 },
154 max_restart => { optional => 1 },
155 max_relocate => { optional => 1 },
156 };
157 }
158
159 sub config_file {
160 my ($class, $vmid, $nodename) = @_;
161
162 return PVE::QemuServer::config_file($vmid, $nodename);
163 }
164
165 sub exists {
166 my ($class, $vmid, $noerr) = @_;
167
168 my $vmlist = PVE::Cluster::get_vmlist();
169
170 if(!defined($vmlist->{ids}->{$vmid})) {
171 die "resource 'vm:$vmid' does not exists in cluster\n" if !$noerr;
172 return undef;
173 } else {
174 return 1;
175 }
176 }
177
178 sub start {
179 my ($class, $haenv, $id) = @_;
180
181 my $nodename = $haenv->nodename();
182
183 my $params = {
184 node => $nodename,
185 vmid => $id
186 };
187
188 my $upid = PVE::API2::Qemu->vm_start($params);
189 $haenv->upid_wait($upid);
190 }
191
192 sub shutdown {
193 my ($class, $haenv, $id) = @_;
194
195 my $nodename = $haenv->nodename();
196 my $shutdown_timeout = 60; # fixme: make this configurable
197
198 my $params = {
199 node => $nodename,
200 vmid => $id,
201 timeout => $shutdown_timeout,
202 forceStop => 1,
203 };
204
205 my $upid = PVE::API2::Qemu->vm_shutdown($params);
206 $haenv->upid_wait($upid);
207 }
208
209
210 sub migrate {
211 my ($class, $haenv, $id, $target, $online) = @_;
212
213 my $nodename = $haenv->nodename();
214
215 my $params = {
216 node => $nodename,
217 vmid => $id,
218 target => $target,
219 online => $online,
220 };
221
222 # explicitly shutdown if $online isn't true (relocate)
223 if (!$online && $class->check_running($id)) {
224 $class->shutdown($haenv, $id);
225 }
226
227 my $upid = PVE::API2::Qemu->migrate_vm($params);
228 $haenv->upid_wait($upid);
229 }
230
231 sub check_running {
232 my ($class, $vmid) = @_;
233
234 return PVE::QemuServer::check_running($vmid, 1);
235 }
236
237
238 # container resource class
239 package PVE::HA::Resources::PVECT;
240
241 use strict;
242 use warnings;
243
244 use PVE::LXC;
245 use PVE::API2::LXC;
246 use PVE::API2::LXC::Status;
247
248 use base qw(PVE::HA::Resources);
249
250 sub type {
251 return 'ct';
252 }
253
254 sub verify_name {
255 my ($class, $name) = @_;
256
257 die "invalid VMID\n" if $name !~ m/^[1-9][0-9]+$/;
258 }
259
260 sub options {
261 return {
262 state => { optional => 1 },
263 group => { optional => 1 },
264 comment => { optional => 1 },
265 max_restart => { optional => 1 },
266 max_relocate => { optional => 1 },
267 };
268 }
269
270 sub config_file {
271 my ($class, $vmid, $nodename) = @_;
272
273 return PVE::LXC::config_file($vmid, $nodename);
274 }
275
276 sub exists {
277 my ($class, $vmid, $noerr) = @_;
278
279 my $vmlist = PVE::Cluster::get_vmlist();
280
281 if(!defined($vmlist->{ids}->{$vmid})) {
282 die "resource 'ct:$vmid' does not exists in cluster\n" if !$noerr;
283 return undef;
284 } else {
285 return 1;
286 }
287 }
288
289 sub start {
290 my ($class, $haenv, $id) = @_;
291
292 my $nodename = $haenv->nodename();
293
294 my $params = {
295 node => $nodename,
296 vmid => $id
297 };
298
299 my $upid = PVE::API2::LXC::Status->vm_start($params);
300 $haenv->upid_wait($upid);
301 }
302
303 sub shutdown {
304 my ($class, $haenv, $id) = @_;
305
306 my $nodename = $haenv->nodename();
307 my $shutdown_timeout = 60; # fixme: make this configurable
308
309 my $params = {
310 node => $nodename,
311 vmid => $id,
312 timeout => $shutdown_timeout,
313 forceStop => 1,
314 };
315
316 my $upid = PVE::API2::LXC::Status->vm_shutdown($params);
317 $haenv->upid_wait($upid);
318 }
319
320 sub migrate {
321 my ($class, $haenv, $id, $target, $online) = @_;
322
323 my $nodename = $haenv->nodename();
324
325 my $params = {
326 node => $nodename,
327 vmid => $id,
328 target => $target,
329 online => 0, # we cannot migrate CT (yet) online, only relocate
330 };
331
332 # always relocate container for now
333 if ($class->check_running($id)) {
334 $class->shutdown($haenv, $id);
335 }
336
337 my $upid = PVE::API2::LXC->migrate_vm($params);
338 $haenv->upid_wait($upid);
339 }
340
341 sub check_running {
342 my ($class, $vmid) = @_;
343
344 return PVE::LXC::check_running($vmid);
345 }
346
347
348 # package PVE::HA::Resources::IPAddr;
349
350 # use strict;
351 # use warnings;
352 # use PVE::Tools qw($IPV4RE $IPV6RE);
353
354 # use base qw(PVE::HA::Resources);
355
356 # sub type {
357 # return 'ipaddr';
358 # }
359
360 # sub verify_name {
361 # my ($class, $name) = @_;
362
363 # die "invalid IP address\n" if $name !~ m!^$IPV6RE|$IPV4RE$!;
364 # }
365
366 # sub options {
367 # return {
368 # state => { optional => 1 },
369 # group => { optional => 1 },
370 # comment => { optional => 1 },
371 # };
372 # }
373
374 1;