]> git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/Helpers.pm
schema: fix description of migrate_downtime parameter
[qemu-server.git] / PVE / QemuServer / Helpers.pm
1 package PVE::QemuServer::Helpers;
2
3 use strict;
4 use warnings;
5
6 use File::stat;
7 use JSON;
8
9 use PVE::INotify;
10 use PVE::ProcFSTools;
11
12 use base 'Exporter';
13 our @EXPORT_OK = qw(
14 min_version
15 config_aware_timeout
16 parse_number_sets
17 windows_version
18 );
19
20 my $nodename = PVE::INotify::nodename();
21
22 # Paths and directories
23
24 our $var_run_tmpdir = "/var/run/qemu-server";
25 mkdir $var_run_tmpdir;
26
27 sub qmp_socket {
28 my ($vmid, $qga) = @_;
29 my $sockettype = $qga ? 'qga' : 'qmp';
30 return "${var_run_tmpdir}/$vmid.$sockettype";
31 }
32
33 sub pidfile_name {
34 my ($vmid) = @_;
35 return "${var_run_tmpdir}/$vmid.pid";
36 }
37
38 sub vnc_socket {
39 my ($vmid) = @_;
40 return "${var_run_tmpdir}/$vmid.vnc";
41 }
42
43 # Parse the cmdline of a running kvm/qemu process and return arguments as hash
44 sub parse_cmdline {
45 my ($pid) = @_;
46
47 my $fh = IO::File->new("/proc/$pid/cmdline", "r");
48 if (defined($fh)) {
49 my $line = <$fh>;
50 $fh->close;
51 return if !$line;
52 my @param = split(/\0/, $line);
53
54 my $cmd = $param[0];
55 return if !$cmd || ($cmd !~ m|kvm$| && $cmd !~ m@(?:^|/)qemu-system-[^/]+$@);
56
57 my $phash = {};
58 my $pending_cmd;
59 for (my $i = 0; $i < scalar (@param); $i++) {
60 my $p = $param[$i];
61 next if !$p;
62
63 if ($p =~ m/^--?(.*)$/) {
64 if ($pending_cmd) {
65 $phash->{$pending_cmd} = {};
66 }
67 $pending_cmd = $1;
68 } elsif ($pending_cmd) {
69 $phash->{$pending_cmd} = { value => $p };
70 $pending_cmd = undef;
71 }
72 }
73
74 return $phash;
75 }
76 return;
77 }
78
79 sub vm_running_locally {
80 my ($vmid) = @_;
81
82 my $pidfile = pidfile_name($vmid);
83
84 if (my $fd = IO::File->new("<$pidfile")) {
85 my $st = stat($fd);
86 my $line = <$fd>;
87 close($fd);
88
89 my $mtime = $st->mtime;
90 if ($mtime > time()) {
91 warn "file '$pidfile' modified in future\n";
92 }
93
94 if ($line =~ m/^(\d+)$/) {
95 my $pid = $1;
96 my $cmdline = parse_cmdline($pid);
97 if ($cmdline && defined($cmdline->{pidfile}) && $cmdline->{pidfile}->{value}
98 && $cmdline->{pidfile}->{value} eq $pidfile) {
99 if (my $pinfo = PVE::ProcFSTools::check_process_running($pid)) {
100 return $pid;
101 }
102 }
103 }
104 }
105
106 return;
107 }
108
109 sub min_version {
110 my ($verstr, $major, $minor, $pve) = @_;
111
112 if ($verstr =~ m/^(\d+)\.(\d+)(?:\.(\d+))?(?:\+pve(\d+))?/) {
113 return 1 if version_cmp($1, $major, $2, $minor, $4, $pve) >= 0;
114 return 0;
115 }
116
117 die "internal error: cannot check version of invalid string '$verstr'";
118 }
119
120 # gets in pairs the versions you want to compares, i.e.:
121 # ($a-major, $b-major, $a-minor, $b-minor, $a-extra, $b-extra, ...)
122 # returns 0 if same, -1 if $a is older than $b, +1 if $a is newer than $b
123 sub version_cmp {
124 my @versions = @_;
125
126 my $size = scalar(@versions);
127
128 return 0 if $size == 0;
129
130 if ($size & 1) {
131 my (undef, $fn, $line) = caller(0);
132 die "cannot compare odd count of versions, called from $fn:$line\n";
133 }
134
135 for (my $i = 0; $i < $size; $i += 2) {
136 my ($a, $b) = splice(@versions, 0, 2);
137 $a //= 0;
138 $b //= 0;
139
140 return 1 if $a > $b;
141 return -1 if $a < $b;
142 }
143 return 0;
144 }
145
146 sub config_aware_timeout {
147 my ($config, $memory, $is_suspended) = @_;
148 my $timeout = 30;
149
150 # Based on user reported startup time for vm with 512GiB @ 4-5 minutes
151 if (defined($memory) && $memory > 30720) {
152 $timeout = int($memory/1024);
153 }
154
155 # When using PCI passthrough, users reported much higher startup times,
156 # growing with the amount of memory configured. Constant factor chosen
157 # based on user reports.
158 if (grep(/^hostpci[0-9]+$/, keys %$config)) {
159 $timeout *= 4;
160 }
161
162 if ($is_suspended && $timeout < 300) {
163 $timeout = 300;
164 }
165
166 if ($config->{hugepages} && $timeout < 150) {
167 $timeout = 150;
168 }
169
170 return $timeout;
171 }
172
173 sub get_node_pvecfg_version {
174 my ($node) = @_;
175
176 my $nodes_version_info = PVE::Cluster::get_node_kv('version-info', $node);
177 return if !$nodes_version_info->{$node};
178
179 my $version_info = decode_json($nodes_version_info->{$node});
180 return $version_info->{version};
181 }
182
183 sub pvecfg_min_version {
184 my ($verstr, $major, $minor, $release) = @_;
185
186 return 0 if !$verstr;
187
188 if ($verstr =~ m/^(\d+)\.(\d+)(?:[.-](\d+))?/) {
189 return 1 if version_cmp($1, $major, $2, $minor, $3 // 0, $release) >= 0;
190 return 0;
191 }
192
193 die "internal error: cannot check version of invalid string '$verstr'";
194 }
195
196 sub parse_number_sets {
197 my ($set) = @_;
198 my $res = [];
199 foreach my $part (split(/;/, $set)) {
200 if ($part =~ /^\s*(\d+)(?:-(\d+))?\s*$/) {
201 die "invalid range: $part ($2 < $1)\n" if defined($2) && $2 < $1;
202 push @$res, [ $1, $2 ];
203 } else {
204 die "invalid range: $part\n";
205 }
206 }
207 return $res;
208 }
209
210 sub windows_version {
211 my ($ostype) = @_;
212
213 return 0 if !$ostype;
214
215 my $winversion = 0;
216
217 if($ostype eq 'wxp' || $ostype eq 'w2k3' || $ostype eq 'w2k') {
218 $winversion = 5;
219 } elsif($ostype eq 'w2k8' || $ostype eq 'wvista') {
220 $winversion = 6;
221 } elsif ($ostype =~ m/^win(\d+)$/) {
222 $winversion = $1;
223 }
224
225 return $winversion;
226 }
227
228 1;