use Fcntl qw(:DEFAULT :flock);
use File::Copy;
use File::Path qw(make_path remove_tree);
-use Term::ReadLine;
+
+use Glib;
+use Gtk3 '-init';
use PVE::HA::CRM;
use PVE::HA::LRM;
$d->{lrm} = undef; # create on power on
}
+ $self->create_main_window();
+
return $self;
}
$id = 'hardware' if !$id;
- printf("%-5s %10s %12s: $msg\n", $level, strftime("%H:%M:%S", localtime($time)), $id);
+ my $text = sprintf("%-5s %10s %12s: $msg\n", $level,
+ strftime("%H:%M:%S", localtime($time)), $id);
+
+ $self->append_text($text);
}
sub fork_daemon {
sub sim_hardware_cmd {
my ($self, $cmdstr, $logid) = @_;
+ my $cstatus;
+
# note: do not fork when we own the lock!
my $code = sub {
my ($lockfh) = @_;
- my $cstatus = $self->read_hardware_status_nolock();
+ $cstatus = $self->read_hardware_status_nolock();
my ($cmd, $node, $action) = split(/\s+/, $cmdstr);
$cstatus->{$node}->{network} = $action;
} elsif ($cmd eq 'network') {
- $cstatus->{$node}->{network} = $action;
+ $cstatus->{$node}->{network} = $action;
} else {
die "sim_hardware_cmd: unknown command '$cmd'\n";
}
$self->write_hardware_status_nolock($cstatus);
};
- return $self->global_lock($code);
-}
+ my $res = $self->global_lock($code);
+ # update GUI outside lock
-sub run {
+ foreach my $node (keys %$cstatus) {
+ my $d = $self->{nodes}->{$node};
+ $d->{network_btn}->set_active($cstatus->{$node}->{network} eq 'on');
+ $d->{power_btn}->set_active($cstatus->{$node}->{power} eq 'on');
+ }
+
+ return $res;
+}
+
+sub cleanup {
my ($self) = @_;
- my $last_command_time = 0;
+ my @nodes = sort keys %{$self->{nodes}};
+ foreach my $node (@nodes) {
+ my $d = $self->{nodes}->{$node};
- print "entering HA simulation shell - type 'help' for help\n";
+ if ($d->{crm}) {
+ kill 9, $d->{crm};
+ delete $d->{crm};
+ }
+ if ($d->{lrm}) {
+ kill 9, $d->{lrm};
+ delete $d->{lrm};
+ }
+ }
+}
- my $term = new Term::ReadLine ('pve-ha-simulator');
- my $attribs = $term->Attribs;
+sub append_text {
+ my ($self, $text) = @_;
+
+ my $logview = $self->{gui}->{text_view} || die "GUI not ready";
+ my $textbuf = $logview->get_buffer();
- my $select = new IO::Select;
+ $textbuf->insert_at_cursor($text, -1);
+ my $lines = $textbuf->get_line_count();
- $select->add(\*STDIN);
+ my $history = 102;
+
+ if ($lines > $history) {
+ my $start = $textbuf->get_iter_at_line(0);
+ my $end = $textbuf->get_iter_at_line($lines - $history);
+ $textbuf->delete($start, $end);
+ }
- my $end_simulation = 0;
+ $logview->scroll_to_mark($textbuf->get_insert(), 0.0, 1, 0.0, 1.0);
+}
+
+sub set_power_state {
+ my ($self, $node) = @_;
+
+ my $d = $self->{nodes}->{$node} || die "no such node '$node'";
+
+ my $action = $d->{power_btn}->get_active() ? 'on' : 'off';
- my $input_cb = sub {
- my $input = shift;
+ $self->sim_hardware_cmd("power $node $action");
+}
- chomp $input;
+sub set_network_state {
+ my ($self, $node) = @_;
- return if $input =~ m/^\s*$/;
+ my $d = $self->{nodes}->{$node} || die "no such node '$node'";
- if ($input =~ m/^\s*q(uit)?\s*$/) {
- $end_simulation = 1;
- }
+ my $action = $d->{network_btn}->get_active() ? 'on' : 'off';
+
+ $self->sim_hardware_cmd("network $node $action");
+}
+
+sub create_main_window {
+ my ($self) = @_;
- $term->addhistory($input);
+ my $w;
- eval {
- $self->sim_hardware_cmd($input);
- };
- warn $@ if $@;
- };
+ my $window = Gtk3::Window->new();
+ $window->set_title("Proxmox HA Simulator");
+
+ $window->signal_connect( destroy => sub { Gtk3::main_quit(); });
+
+ # create logview
+
+ my $vbox = Gtk3::VBox->new(0, 0);
+ my $f1 = Gtk3::Frame->new('Cluster Log');
+ $vbox->pack_start($f1, 1, 1, 0);
- $term->CallbackHandlerInstall("ha> ", $input_cb);
+ my $logview = Gtk3::TextView->new();
+ $logview->set_editable(0);
+ $logview->set_cursor_visible(0);
- while ($select->count) {
- my @handles = $select->can_read(1);
+ $self->{gui}->{text_view} = $logview;
+ my $swindow = Gtk3::ScrolledWindow->new();
+ $swindow->set_size_request(800, 400);
+ $swindow->add($logview);
+
+ $f1->add($swindow);
+
+ # create node control
+
+ my $hbox = Gtk3::HBox->new(0, 10);
+ $vbox->pack_start($hbox, 0, 0, 0);
+
+ my $ngrid = Gtk3::Grid->new();
+ $hbox->add($ngrid);
+
+ $w = Gtk3::Label->new('Node');
+ $ngrid->attach($w, 0, 0, 1, 1);
+ $w = Gtk3::Label->new('Power');
+ $ngrid->attach($w, 1, 0, 1, 1);
+ $w = Gtk3::Label->new('Network');
+ $ngrid->attach($w, 2, 0, 1, 1);
+
+ my $row = 1;
+ my @nodes = sort keys %{$self->{nodes}};
+ foreach my $node (@nodes) {
+ my $d = $self->{nodes}->{$node};
+
+ $w = Gtk3::Label->new($node);
+ $ngrid->attach($w, 0, $row, 1, 1);
+ $w = Gtk3::Switch->new();
+ $ngrid->attach($w, 1, $row, 1, 1);
+ $d->{power_btn} = $w;
+ $w->signal_connect('notify::active' => sub {
+ $self->set_power_state($node);
+ }),
+
+ $w = Gtk3::Switch->new();
+ $ngrid->attach($w, 2, $row, 1, 1);
+ $d->{network_btn} = $w;
+ $w->signal_connect('notify::active' => sub {
+ $self->set_network_state($node);
+ }),
+
+ $row++;
+ }
+
+
+ $window->add ($vbox);
+
+ $window->show_all;
+ $window->realize ();
+}
+
+sub run {
+ my ($self) = @_;
+
+ Glib::Timeout->add(1000, sub {
+
+ # check all watchdogs
my @nodes = sort keys %{$self->{nodes}};
foreach my $node (@nodes) {
if (!$self->watchdog_check($node)) {
$self->log('info', "server '$node' stopped by poweroff (watchdog)");
}
}
+ });
- if (scalar(@handles)) {
- $term->rl_callback_read_char();
- }
-
- last if $end_simulation;
- }
-
- kill(9, 0); kill whole process group
+ Gtk3->main;
- $term->rl_deprep_terminal();
+ $self->cleanup();
}
1;