1 package PVE
::HA
::Sim
::TestHardware
;
3 # Simulate Hardware resources
5 # power supply for nodes: on/off
6 # network connection to nodes: on/off
7 # watchdog devices for nodes
11 use POSIX
qw(strftime EINTR);
15 use Fcntl
qw(:DEFAULT :flock);
17 use File
::Path
qw(make_path remove_tree);
22 use PVE
::HA
::Sim
::TestEnv
;
23 use base
qw(PVE::HA::Sim::Hardware);
25 my $max_sim_time = 10000;
28 my ($this, $testdir) = @_;
30 my $class = ref($this) || $this;
32 my $self = $class->SUPER::new
($testdir);
34 my $raw = PVE
::Tools
::file_get_contents
("$testdir/cmdlist");
35 $self->{cmdlist
} = decode_json
($raw);
37 $self->{loop_count
} = 0;
38 $self->{cur_time
} = 0;
40 my $statusdir = $self->statusdir();
41 my $logfile = "$statusdir/log";
42 $self->{logfh
} = IO
::File-
>new(">>$logfile") ||
43 die "unable to open '$logfile' - $!";
45 foreach my $node (sort keys %{$self->{nodes
}}) {
47 my $d = $self->{nodes
}->{$node};
50 PVE
::HA
::Env-
>new('PVE::HA::Sim::TestEnv', $node, $self, 'crm');
53 PVE
::HA
::Env-
>new('PVE::HA::Sim::TestEnv', $node, $self, 'lrm');
55 $d->{crm
} = undef; # create on power on
56 $d->{lrm
} = undef; # create on power on
65 return $self->{cur_time
};
69 my ($self, $level, $msg, $id) = @_;
73 my $time = $self->get_time();
75 $id = 'hardware' if !$id;
77 my $line = sprintf("%-5s %5d %12s: $msg\n", $level, $time, $id);
80 $self->{logfh
}->print($line);
81 $self->{logfh
}->flush();
84 # simulate hardware commands
85 # power <node> <on|off>
86 # network <node> <on|off>
90 # service <sid> <enabled|disabled>
91 # service <sid> <migrate|relocate> <target>
93 sub sim_hardware_cmd
{
94 my ($self, $cmdstr, $logid) = @_;
98 my $cstatus = $self->read_hardware_status_nolock();
100 my ($cmd, $objid, $action, $target) = split(/\s+/, $cmdstr);
102 die "sim_hardware_cmd: no node or service for command specified"
105 my ($node, $sid, $d);
107 if ($cmd eq 'service') {
108 $sid = PVE
::HA
::Tools
::pve_verify_ha_resource_id
($objid);
111 $d = $self->{nodes
}->{$node} ||
112 die "sim_hardware_cmd: no such node '$node'\n";
115 $self->log('info', "execute $cmdstr", $logid);
117 if ($cmd eq 'power') {
118 die "sim_hardware_cmd: unknown action '$action'" if $action !~ m/^(on|off)$/;
119 if ($cstatus->{$node}->{power
} ne $action) {
120 if ($action eq 'on') {
121 $d->{crm
} = PVE
::HA
::CRM-
>new($d->{crm_env
}) if !$d->{crm
};
122 $d->{lrm
} = PVE
::HA
::LRM-
>new($d->{lrm_env
}) if !$d->{lrm
};
123 $d->{lrm_restart
} = undef;
126 $d->{crm_env
}->log('info', "killed by poweroff");
130 $d->{lrm_env
}->log('info', "killed by poweroff");
132 $d->{lrm_restart
} = undef;
134 $self->watchdog_reset_nolock($node);
135 $self->write_service_status($node, {});
139 $cstatus->{$node}->{power
} = $action;
140 $cstatus->{$node}->{network
} = $action;
141 $cstatus->{$node}->{shutdown} = undef;
143 $self->write_hardware_status_nolock($cstatus);
145 } elsif ($cmd eq 'network') {
146 die "sim_hardware_cmd: unknown network action '$action'"
147 if $action !~ m/^(on|off)$/;
148 $cstatus->{$node}->{network
} = $action;
150 $self->write_hardware_status_nolock($cstatus);
152 } elsif ($cmd eq 'reboot' || $cmd eq 'shutdown') {
153 $cstatus->{$node}->{shutdown} = $cmd;
155 $self->write_hardware_status_nolock($cstatus);
157 $d->{lrm
}->shutdown_request() if $d->{lrm
};
158 } elsif ($cmd eq 'restart-lrm') {
160 $d->{lrm_restart
} = 1;
161 $d->{lrm
}->shutdown_request();
163 } elsif ($cmd eq 'service') {
164 if ($action eq 'enabled' || $action eq 'disabled') {
166 $self->set_service_state($sid, $action);
168 } elsif ($action eq 'migrate' || $action eq 'relocate') {
170 die "sim_hardware_cmd: missing target node for '$action' command"
173 $self->queue_crm_commands_nolock("$action $sid $target");
176 die "sim_hardware_cmd: unknown service action '$action' " .
177 "- not implemented\n"
180 die "sim_hardware_cmd: unknown command '$cmdstr'\n";
185 return $self->global_lock($code);
191 my $last_command_time = 0;
196 my $starttime = $self->get_time();
198 my @nodes = sort keys %{$self->{nodes
}};
200 my $nodecount = scalar(@nodes);
202 my $looptime = $nodecount*2;
203 $looptime = 20 if $looptime < 20;
205 die "unable to simulate so many nodes. You need to increate watchdog/lock timeouts.\n"
208 foreach my $node (@nodes) {
210 my $d = $self->{nodes
}->{$node};
212 if (my $crm = $d->{crm
}) {
214 $d->{crm_env
}->loop_start_hook($self->get_time());
216 die "implement me (CRM exit)" if !$crm->do_one_iteration();
218 $d->{crm_env
}->loop_end_hook();
220 my $nodetime = $d->{crm_env
}->get_time();
221 $self->{cur_time
} = $nodetime if $nodetime > $self->{cur_time
};
224 if (my $lrm = $d->{lrm
}) {
226 $d->{lrm_env
}->loop_start_hook($self->get_time());
228 my $exit_lrm = !$lrm->do_one_iteration();
230 $d->{lrm_env
}->loop_end_hook();
232 my $nodetime = $d->{lrm_env
}->get_time();
233 $self->{cur_time
} = $nodetime if $nodetime > $self->{cur_time
};
236 $d->{lrm_env
}->log('info', "exit (loop end)");
238 my $cstatus = $self->read_hardware_status_nolock();
239 my $nstatus = $cstatus->{$node} || die "no node status for node '$node'";
240 my $shutdown = $nstatus->{shutdown} || '';
241 if ($d->{lrm_restart
}) {
242 die "lrm restart during shutdown - not implemented" if $shutdown;
243 $d->{lrm_restart
} = undef;
244 $d->{lrm
} = PVE
::HA
::LRM-
>new($d->{lrm_env
});
245 } elsif ($shutdown eq 'reboot') {
246 $self->sim_hardware_cmd("power $node off", 'reboot');
247 $self->sim_hardware_cmd("power $node on", 'reboot');
248 } elsif ($shutdown eq 'shutdown') {
249 $self->sim_hardware_cmd("power $node off", 'shutdown');
251 die "unexpected LRM exit - not implemented"
256 foreach my $n (@nodes) {
257 if (!$self->watchdog_check($n)) {
258 $self->sim_hardware_cmd("power $n off", 'watchdog');
259 $self->log('info', "server '$n' stopped by poweroff (watchdog)");
260 $self->{nodes
}->{$n}->{crm
} = undef;
261 $self->{nodes
}->{$n}->{lrm
} = undef;
267 $self->{cur_time
} = $starttime + $looptime
268 if ($self->{cur_time
} - $starttime) < $looptime;
270 die "simulation end\n" if $self->{cur_time
} > $max_sim_time;
272 foreach my $node (@nodes) {
273 my $d = $self->{nodes
}->{$node};
275 $d->{lrm_env
}->loop_start_hook($self->get_time());
276 $d->{crm_env
}->loop_start_hook($self->get_time());
279 next if $self->{cur_time
} < $next_cmd_at;
281 # apply new comand after 5 loop iterations
283 if (($self->{loop_count
} % 5) == 0) {
284 my $list = shift @{$self->{cmdlist
}};
286 # end sumulation (500 seconds after last command)
287 return if (($self->{cur_time
} - $last_command_time) > 500);
290 foreach my $cmd (@$list) {
291 $last_command_time = $self->{cur_time
};
293 if ($cmd =~ m/^delay\s+(\d+)\s*$/) {
294 $next_cmd_at = $self->{cur_time
} + $1;
296 $self->sim_hardware_cmd($cmd, 'cmdlist');
301 ++$self->{loop_count
};