]> git.proxmox.com Git - pve-ha-manager.git/blob - PVE/HA/SimEnv.pm
add service_state to track VM locations with SimEnv
[pve-ha-manager.git] / PVE / HA / SimEnv.pm
1 package PVE::HA::SimEnv;
2
3 use strict;
4 use warnings;
5 use POSIX qw(strftime);
6 use Data::Dumper;
7 use JSON;
8
9 use PVE::HA::Env;
10
11 use base qw(PVE::HA::Env);
12
13 my $cur_time = 0;
14
15 my $max_sim_time = 1000;
16
17 # time => quorate nodes (first node gets manager lock)
18 my $quorum_setup = [];
19
20 my $compute_node_info = sub {
21
22 my $last_node_info = {};
23
24 foreach my $entry (@$quorum_setup) {
25 my ($time, $members) = @$entry;
26
27 $max_sim_time = $time + 1000;
28
29 my $node_info = {};
30
31 foreach my $node (@$members) {
32 $node_info->{$node}->{online} = 1;
33 if (!$last_node_info->{$node}) {
34 $node_info->{$node}->{join_time} = $time;
35 } else {
36 $node_info->{$node}->{join_time} =
37 $last_node_info->{$node}->{join_time};
38 }
39 }
40
41 push @$entry, $node_info;
42
43 $last_node_info = $node_info;
44 }
45 };
46
47 my $lookup_quorum_info = sub {
48 my ($self) = @_;
49
50 foreach my $entry (reverse @$quorum_setup) {
51 my ($time, $members) = @$entry;
52
53 if ($cur_time >= $time) {
54 return $members;
55 }
56 }
57
58 return undef;
59 };
60
61 my $node_is_lock_owner = sub {
62 my ($self) = @_;
63
64 if (my $members = &$lookup_quorum_info($self)) {
65 return $members->[0] eq $self->{nodename} ? 1 : 0;
66 }
67
68 return 0;
69 };
70
71 sub new {
72 my ($this, $testdir) = @_;
73
74 my $class = ref($this) || $this;
75
76 my $nodename = 'node1';
77 if (-f "$testdir/hostname") {
78 $nodename = PVE::Tools::file_read_firstline("$testdir/hostname");
79 }
80
81 if (-f "$testdir/membership") {
82 my $raw = PVE::Tools::file_get_contents("$testdir/membership");
83 $quorum_setup = decode_json($raw);
84 }
85
86 my $statusdir = "$testdir/status";
87
88 my $self = $class->SUPER::new($statusdir, $nodename);
89
90 &$compute_node_info();
91
92 return $self;
93 }
94
95 sub read_manager_status {
96 my ($self) = @_;
97
98 die "detected read without lock\n"
99 if !&$node_is_lock_owner($self);
100
101 my $filename = "$self->{statusdir}/manager_status";
102
103 my $raw = PVE::Tools::file_get_contents($filename);
104
105 return decode_json($raw) || {};
106 }
107
108 sub write_manager_status {
109 my ($self, $status_obj) = @_;
110
111 die "detected write without lock\n"
112 if !&$node_is_lock_owner($self);
113
114 my $data = encode_json($status_obj);
115 my $filename = "$self->{statusdir}/manager_status";
116
117 PVE::Tools::file_set_contents($filename, $data);
118 }
119
120 sub manager_status_exists {
121 my ($self) = @_;
122
123 my $filename = "$self->{statusdir}/manager_status";
124
125 return -f $filename ? 1 : 0;
126 }
127
128 my $read_service_status = sub {
129 my ($self) = @_;
130
131 my $filename = "$self->{statusdir}/service_status";
132
133 if (-f $filename) {
134 my $raw = PVE::Tools::file_get_contents($filename);
135 return decode_json($raw);
136 } else {
137 return {};
138 }
139 };
140
141 my $write_service_status = sub {
142 my ($self, $status_obj) = @_;
143
144 my $data = encode_json($status_obj);
145 my $filename = "$self->{statusdir}/service_status";
146
147 PVE::Tools::file_set_contents($filename, $data);
148 };
149
150 sub read_service_config {
151 my ($self) = @_;
152
153 my $conf = {
154 'pvevm:101' => {
155 type => 'pvevm',
156 name => '101',
157 state => 'enabled',
158 },
159 'pvevm:102' => {
160 type => 'pvevm',
161 name => '102',
162 state => 'disabled',
163 },
164 'pvevm:103' => {
165 type => 'pvevm',
166 name => '103',
167 state => 'enabled',
168 },
169 };
170
171 my $rl = &$read_service_status($self);
172
173 foreach my $sid (keys %$conf) {
174 die "service '$sid' does not exists\n"
175 if !($rl->{$sid} && $rl->{$sid}->{node});
176 }
177
178 foreach my $sid (keys %$rl) {
179 next if !$conf->{$sid};
180 $conf->{$sid}->{current_node} = $rl->{$sid}->{node};
181 $conf->{$sid}->{node} = $conf->{$sid}->{current_node};
182 }
183
184 return $conf;
185 }
186
187 sub log {
188 my ($self, $level, $msg) = @_;
189
190 chomp $msg;
191
192 my $time = $self->get_time();
193
194 printf("%-5s %10d $self->{nodename}: $msg\n", $level, $time);
195 }
196
197 sub get_time {
198 my ($self) = @_;
199
200 return $cur_time;
201 }
202
203 sub sleep {
204 my ($self, $delay) = @_;
205
206 $cur_time += $delay;
207 }
208
209 sub get_ha_manager_lock {
210 my ($self) = @_;
211
212 my $res = &$node_is_lock_owner($self);
213 ++$cur_time;
214 return $res;
215 }
216
217 # return true when cluster is quorate
218 sub quorate {
219 my ($self) = @_;
220
221 if (my $members = &$lookup_quorum_info($self)) {
222 foreach my $node (@$members) {
223 return 1 if $node eq $self->{nodename};
224 }
225 }
226
227 return 0;
228 }
229
230 sub get_node_info {
231 my ($self) = @_;
232
233 foreach my $entry (reverse @$quorum_setup) {
234 my ($time, $members, $node_info) = @$entry;
235
236 if ($cur_time >= $time) {
237 return $node_info;
238 }
239 }
240
241 die "unbale to get node info";
242 }
243
244 sub loop_start_hook {
245 my ($self) = @_;
246
247 $self->{loop_start_time} = $cur_time;
248
249 # do nothing
250 }
251
252 sub loop_end_hook {
253 my ($self) = @_;
254
255 my $delay = $cur_time - $self->{loop_start_time};
256
257 die "loop take too long ($delay seconds)\n" if $delay > 30;
258
259 die "simulation end\n" if $cur_time > $max_sim_time;
260 }
261
262 1;