use strict;
use warnings;
use POSIX qw(strftime EINTR);
-use Data::Dumper;
use JSON;
use IO::File;
use Fcntl qw(:DEFAULT :flock);
use File::Copy;
use File::Path qw(make_path remove_tree);
-use PVE::HA::Config;
+use PVE::HA::FenceConfig;
+use PVE::HA::Groups;
my $watchdog_timeout = 60;
die "service '$sid' without assigned node!" if !$d->{node};
- if ($sid =~ m/^vm:(\d+)$/) {
- $d->{type} = 'vm';
- $d->{name} = $1;
+ if ($sid =~ m/^(vm|ct|fa):(\d+)$/) {
+ $d->{type} = $1;
+ $d->{name} = $2;
} else {
die "implement me";
}
$d->{state} = 'disabled' if !$d->{state};
+ $d->{state} = 'started' if $d->{state} eq 'enabled'; # backward compatibility
+ $d->{max_restart} = 1 if !defined($d->{max_restart});
+ $d->{max_relocate} = 1 if !defined($d->{max_relocate});
}
return $conf;
my $filename = "$self->{statusdir}/service_config";
return PVE::HA::Tools::write_json_to_file($filename, $conf);
-}
+}
+
+sub read_fence_config {
+ my ($self) = @_;
+
+ my $raw = undef;
+
+ my $filename = "$self->{statusdir}/fence.cfg";
+ if (-e $filename) {
+ $raw = PVE::Tools::file_get_contents($filename);
+ }
+
+ return PVE::HA::FenceConfig::parse_config($filename, $raw);
+}
+
+sub exec_fence_agent {
+ my ($self, $agent, $node, @param) = @_;
+
+ # let all agent succeed and behave the same for now
+ $self->sim_hardware_cmd("power $node off", $agent);
+
+ return 0; # EXIT_SUCCESS
+}
sub set_service_state {
my ($self, $sid, $state) = @_;
$self->write_service_config($conf);
}
+sub service_has_lock {
+ my ($self, $sid) = @_;
+
+ my $conf = $self->read_service_config();
+
+ die "no such service '$sid'\n" if !$conf->{$sid};
+
+ return $conf->{$sid}->{lock};
+}
+
+sub lock_service {
+ my ($self, $sid, $lock) = @_;
+
+ my $conf = $self->read_service_config();
+
+ die "no such service '$sid'\n" if !$conf->{$sid};
+
+ $conf->{$sid}->{lock} = $lock || 'backup';
+
+ $self->write_service_config($conf);
+
+ return $conf;
+}
+
+sub unlock_service {
+ my ($self, $sid, $lock) = @_;
+
+ my $conf = $self->read_service_config();
+
+ die "no such service '$sid'\n" if !$conf->{$sid};
+
+ if (!defined($conf->{$sid}->{lock})) {
+ return undef;
+ }
+
+ if (defined($lock) && $conf->{$sid}->{lock} ne $lock) {
+ warn "found lock '$conf->{$sid}->{lock}' trying to remove '$lock' lock\n";
+ return undef;
+ }
+
+ my $removed_lock = delete $conf->{$sid}->{lock};
+
+ $self->write_service_config($conf);
+
+ return $removed_lock;
+}
+
sub queue_crm_commands_nolock {
my ($self, $cmd) = @_;
my $raw = '';
$raw = PVE::Tools::file_get_contents($filename) if -f $filename;
- return PVE::HA::Config::parse_groups_config($filename, $raw);
+ return PVE::HA::Groups->parse_config($filename, $raw);
}
sub read_service_status {
die "missing testdir" if !$testdir;
+ die "testdir '$testdir' does not exist or is not a directory!\n"
+ if !-d $testdir;
+
my $class = ref($this) || $this;
my $self = bless {}, $class;
$self->write_hardware_status_nolock($cstatus);
}
+ if (-f "$testdir/fence.cfg") {
+ copy("$testdir/fence.cfg", "$statusdir/fence.cfg");
+ }
my $cstatus = $self->read_hardware_status_nolock();
return ($node_info, $quorate);
}
+# helper for Sim/ only
+sub get_cfs_state {
+ my ($self, $node, $state) = @_;
+
+ # TODO: ensure nolock is OK when adding this to RTSim
+ my $cstatus = $self->read_hardware_status_nolock();
+ my $res = $cstatus->{$node}->{cfs}->{$state};
+
+ # we assume default true if not defined
+ return !defined($res) || $res;
+}
+
# simulate hardware commands
# power <node> <on|off>
# network <node> <on|off>
+# cfs <node> <rw|update> <work|fail>
+# reboot <node>
+# shutdown <node>
+# restart-lrm <node>
+# service <sid> <started|disabled|stopped|ignored>
+# service <sid> <migrate|relocate> <target>
+# service <sid> lock/unlock [lockname]
sub sim_hardware_cmd {
my ($self, $cmdstr, $logid) = @_;
+ my $code = sub {
+ my ($lock_fh) = @_;
+
+ my $cstatus = $self->read_hardware_status_nolock();
+
+ my ($cmd, $objid, $action, $target) = split(/\s+/, $cmdstr);
+
+ die "sim_hardware_cmd: no node or service for command specified"
+ if !$objid;
+
+ my ($node, $sid, $d);
+
+ if ($cmd eq 'service') {
+ $sid = PVE::HA::Tools::pve_verify_ha_resource_id($objid);
+ } else {
+ $node = $objid;
+ $d = $self->{nodes}->{$node} ||
+ die "sim_hardware_cmd: no such node '$node'\n";
+ }
+
+ $self->log('info', "execute $cmdstr", $logid);
+
+ if ($cmd eq 'power') {
+ die "sim_hardware_cmd: unknown action '$action'\n"
+ if $action !~ m/^(on|off)$/;
+
+ if ($cstatus->{$node}->{power} ne $action) {
+ if ($action eq 'on') {
+
+ $d->{crm} = $self->crm_control('start', $d, $lock_fh) if !defined($d->{crm});
+ $d->{lrm} = $self->lrm_control('start', $d, $lock_fh) if !defined($d->{lrm});
+ $d->{lrm_restart} = undef;
+ $cstatus->{$node}->{cfs} = {};
+
+ } else {
+
+ if ($d->{crm}) {
+ $d->{crm_env}->log('info', "killed by poweroff");
+ $self->crm_control('stop', $d, $lock_fh);
+ $d->{crm} = undef;
+ }
+ if ($d->{lrm}) {
+ $d->{lrm_env}->log('info', "killed by poweroff");
+ $self->lrm_control('stop', $d, $lock_fh);
+ $d->{lrm} = undef;
+ $d->{lrm_restart} = undef;
+ }
+
+ $self->watchdog_reset_nolock($node);
+ $self->write_service_status($node, {});
+ }
+ }
+
+ $cstatus->{$node}->{power} = $action;
+ $cstatus->{$node}->{network} = $action;
+ $cstatus->{$node}->{shutdown} = undef;
+
+ $self->write_hardware_status_nolock($cstatus);
+
+ } elsif ($cmd eq 'network') {
+ die "sim_hardware_cmd: unknown network action '$action'"
+ if $action !~ m/^(on|off)$/;
+ $cstatus->{$node}->{network} = $action;
+
+ $self->write_hardware_status_nolock($cstatus);
+
+ } elsif ($cmd eq 'cfs') {
+ die "sim_hardware_cmd: unknown cfs action '$action' for node '$node'"
+ if $action !~ m/^(rw|update)$/;
+ die "sim_hardware_cmd: unknown cfs command '$target' for '$action' on node '$node'"
+ if $target !~ m/^(work|fail)$/;
+
+ $cstatus->{$node}->{cfs}->{$action} = $target eq 'work';
+ $self->write_hardware_status_nolock($cstatus);
+
+ } elsif ($cmd eq 'reboot' || $cmd eq 'shutdown') {
+ $cstatus->{$node}->{shutdown} = $cmd;
+
+ $self->write_hardware_status_nolock($cstatus);
+
+ $self->lrm_control('shutdown', $d, $lock_fh) if defined($d->{lrm});
+ } elsif ($cmd eq 'restart-lrm') {
+ if ($d->{lrm}) {
+ $d->{lrm_restart} = 1;
+ $self->lrm_control('shutdown', $d, $lock_fh);
+ }
+ } elsif ($cmd eq 'crm') {
+
+ if ($action eq 'stop') {
+ if ($d->{crm}) {
+ $d->{crm_stop} = 1;
+ $self->crm_control('shutdown', $d, $lock_fh);
+ }
+ } elsif ($action eq 'start') {
+ $d->{crm} = $self->crm_control('start', $d, $lock_fh) if !defined($d->{crm});
+ } else {
+ die "sim_hardware_cmd: unknown action '$action'";
+ }
+
+ } elsif ($cmd eq 'service') {
+ if ($action eq 'started' || $action eq 'disabled' ||
+ $action eq 'stopped' || $action eq 'ignored') {
+
+ $self->set_service_state($sid, $action);
+
+ } elsif ($action eq 'migrate' || $action eq 'relocate') {
+
+ die "sim_hardware_cmd: missing target node for '$action' command"
+ if !$target;
+
+ $self->queue_crm_commands_nolock("$action $sid $target");
+
+ } elsif ($action eq 'add') {
+
+ $self->add_service($sid, {state => 'started', node => $target});
+
+ } elsif ($action eq 'delete') {
+
+ $self->delete_service($sid);
+
+ } elsif ($action eq 'lock') {
+
+ $self->lock_service($sid, $target);
+
+ } elsif ($action eq 'unlock') {
+
+ $self->unlock_service($sid, $target);
+
+ } else {
+ die "sim_hardware_cmd: unknown service action '$action' " .
+ "- not implemented\n"
+ }
+ } else {
+ die "sim_hardware_cmd: unknown command '$cmdstr'\n";
+ }
+
+ return $cstatus;
+ };
+
+ return $self->global_lock($code);
+}
+
+# for controlling the resource manager services
+sub crm_control {
+ my ($self, $action, $data, $lock_fh) = @_;
+
+ die "implement in subclass";
+}
+
+sub lrm_control {
+ my ($self, $action, $data, $lock_fh) = @_;
+
die "implement in subclass";
}