* Actually added the new Anvil::Tools::DRBD module, as well as a new ::Server module that will handle anything related to the virtual servers.

* Started a re-write of the ocf:alteeve:server resource agent. It has bugs and is missing logic, and trying to clean it up given most of the bugs come from trying to clean up the original agent doesn't make sense. Moving much of the logic into module methods as the functions will be needed elsewhere anyway.

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 5 years ago
parent 63a86b685a
commit 312c949648
  1. 15
      Anvil/Tools.pm
  2. 34
      Anvil/Tools/DRBD.pm
  3. 198
      Anvil/Tools/Server.pm
  4. 204
      ocf/alteeve/server

@ -47,6 +47,7 @@ use Anvil::Tools::Get;
use Anvil::Tools::Job;
use Anvil::Tools::Log;
use Anvil::Tools::Remote;
use Anvil::Tools::Server;
use Anvil::Tools::Storage;
use Anvil::Tools::System;
use Anvil::Tools::Template;
@ -125,6 +126,7 @@ sub new
LOG => Anvil::Tools::Log->new(),
JOB => Anvil::Tools::Job->new(),
REMOTE => Anvil::Tools::Remote->new(),
SERVER => Anvil::Tools::Server->new(),
STORAGE => Anvil::Tools::Storage->new(),
SYSTEM => Anvil::Tools::System->new(),
TEMPLATE => Anvil::Tools::Template->new(),
@ -163,6 +165,7 @@ sub new
$anvil->Log->parent($anvil);
$anvil->Job->parent($anvil);
$anvil->Remote->parent($anvil);
$anvil->Server->parent($anvil);
$anvil->Storage->parent($anvil);
$anvil->System->parent($anvil);
$anvil->Template->parent($anvil);
@ -520,6 +523,18 @@ sub Remote
return ($self->{HANDLE}{REMOTE});
}
=head2 Server
Access the C<Server.pm> methods via 'C<< $anvil->Server->method >>'.
=cut
sub Server
{
my $self = shift;
return ($self->{HANDLE}{SERVER});
}
=head2 Storage
Access the C<Storage.pm> methods via 'C<< $anvil->Storage->method >>'.

@ -83,7 +83,7 @@ This parses the DRBD status on the local or remote system. The data collected is
- drbd::status::<hostname>::resource::<resource_name>::connection::<peer_hostname>::volume::<volume>::{db-dt MiB-s,db0-dt0 MiB-s,db1-dt1 MiB-s,estimated-seconds-to-finish,percent-resync-done,rs-db0-sectors,rs-db1-sectors,rs-dt-start-ms,rs-dt0-ms,rs-dt1-ms,rs-failed,rs-paused-ms,rs-same-csum,rs-total,want}
- drbd::status::<hostname>::resource::<resource>::devices::volume::<volume>::{al-writes,bm-writes,client,disk-state,lower-pending,minor,quorum,read,size,upper-pending,written}
If any data was stored in a previous call, it will be deleted before the new data is collected and stored.
If any data for the host was stored in a previous call, it will be deleted before the new data is collected and stored.
Parameters;
@ -126,11 +126,18 @@ sub get_status
# Is this a local call or a remote call?
my $shell_call = $anvil->data->{path}{exe}{drbdsetup}." status --json";
my $output = "";
my $host = $anvil->_short_hostname;
my $host = $anvil->_short_hostname({debug => $debug});
if (($target) && ($target ne "local") && ($target ne $anvil->_hostname) && ($target ne $anvil->_short_hostname))
{
# Clear the hash where we'll store the data.
$host = $target;
if (exists $anvil->data->{drbd}{status}{$host})
{
delete $anvil->data->{drbd}{status}{$host};
}
# Remote call.
($output, my $error, my $return_code) = $anvil->Remote->call({
($output, my $error, $anvil->data->{drbd}{status}{$host}{return_code}) = $anvil->Remote->call({
debug => $debug,
shell_call => $shell_call,
target => $target,
@ -139,25 +146,24 @@ sub get_status
remote_user => $remote_user,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
error => $error,
output => $output,
return_code => $return_code,
error => $error,
output => $output,
"drbd::status::${host}::return_code" => $anvil->data->{drbd}{status}{return_code},
}});
$host = $target;
}
else
{
# Clear the hash where we'll store the data.
if (exists $anvil->data->{drbd}{status}{$host})
{
delete $anvil->data->{drbd}{status}{$host};
}
# Local.
($output, my $return_code) = $anvil->System->call({shell_call => $shell_call});
($output, $anvil->data->{drbd}{status}{return_code}) = $anvil->System->call({shell_call => $shell_call});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { output => $output, return_code => $return_code }});
}
# Clear the hash where we'll store the data.
if (exists $anvil->data->{drbd}{status}{$host})
{
delete $anvil->data->{drbd}{status}{$host};
}
# Parse the output.
my $json = JSON->new->allow_nonref;
my $drbd_status = $json->decode($output);

@ -0,0 +1,198 @@
package Anvil::Tools::Server;
#
# This module contains methods used to manager servers
#
use strict;
use warnings;
use Scalar::Util qw(weaken isweak);
use Data::Dumper;
our $VERSION = "3.0.0";
my $THIS_FILE = "Server.pm";
### Methods;
# status
=pod
=encoding utf8
=head1 NAME
Anvil::Tools::Server
Provides all methods related to (virtual) servers.
=head1 SYNOPSIS
use Anvil::Tools;
# Get a common object handle on all Anvil::Tools modules.
my $anvil = Anvil::Tools->new();
# Access to methods using '$anvil->Server->X'.
#
#
=head1 METHODS
Methods in this module;
=cut
sub new
{
my $class = shift;
my $self = {
};
bless $self, $class;
return ($self);
}
# Get a handle on the Anvil::Tools object. I know that technically that is a sibling module, but it makes more
# sense in this case to think of it as a parent.
sub parent
{
my $self = shift;
my $parent = shift;
$self->{HANDLE}{TOOLS} = $parent if $parent;
# Defend against memory leads. See Scalar::Util'.
if (not isweak($self->{HANDLE}{TOOLS}))
{
weaken($self->{HANDLE}{TOOLS});;
}
return ($self->{HANDLE}{TOOLS});
}
#############################################################################################################
# Public methods #
#############################################################################################################
=head2 get_status
This reads in a server's XML definition file from disk, if available, and from memory, if the server is running. The XML is analyzed and data is stored in the following locations;
-
Any pre-existing data on the server is flushed before the new information is processed.
Parameters;
=head3 password (optional)
This is the password to use when connecting to a remote machine. If not set, but C<< target >> is, an attempt to connect without a password will be made.
=head3 port (optional)
This is the TCP port to use when connecting to a remote machine. If not set, but C<< target >> is, C<< 22 >> will be used.
=head3 remote_user (optional, default 'root')
If C<< target >> is set, this will be the user we connect to the remote machine as.
=head3 server (required)
This is the name of the server we're gathering data on.
=head3 target (optional)
This is the IP or host name of the machine to read the version of. If this is not set, the local system's version is checked.
=cut
# NOTE: the version is set in anvil.spec by sed'ing the release and arch onto anvil.version in anvil-core's %post
sub get_status
{
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
my $password = defined $parameter->{password} ? $parameter->{password} : "";
my $port = defined $parameter->{port} ? $parameter->{port} : "";
my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root";
my $server = defined $parameter->{server} ? $parameter->{server} : "";
my $target = defined $parameter->{target} ? $parameter->{target} : "local";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
password => $anvil->Log->secure ? $password : $anvil->Words->string({key => "log_0186"}),
port => $port,
remote_user => $remote_user,
target => $target,
}});
if (not $server)
{
return(1);
}
if (exists $anvil->data->{server}{$server})
{
delete $anvil->data->{server}{$server};
}
# Is this a local call or a remote call?
my $shell_call = $anvil->data->{path}{exe}{virsh}." dumpxml ".$server;
if (($target) && ($target ne "local") && ($target ne $anvil->_hostname) && ($target ne $anvil->_short_hostname))
{
# Remote call.
$host = $target;
($anvil->data->{server}{$server}{running}{xml}, my $error, $anvil->data->{server}{$server}{running}{return_code}) = $anvil->Remote->call({
debug => $debug,
shell_call => $shell_call,
target => $target,
port => $port,
password => $password,
remote_user => $remote_user,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
error => $error,
"server::${server}::running::xml" => $anvil->data->{server}{$server}{running}{xml},
"server::${server}::running::return_code" => $anvil->data->{server}{$server}{running}{return_code},
}});
}
else
{
# Local.
($anvil->data->{server}{$server}{running}{xml}, $anvil->data->{server}{$server}{running}{return_code}) = $anvil->System->call({shell_call => $shell_call});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"server::${server}::running::xml" => $anvil->data->{server}{$server}{running}{xml},
"server::${server}::running::return_code" => $anvil->data->{server}{$server}{running}{return_code},
}});
}
# If the return code was non-zero, we can't parse the XML.
if ($anvil->data->{server}{$server}{running}{return_code})
{
$anvil->data->{server}{$server}{running}{xml} = "";
}
# Now get the on-disk XML.
($anvil->data->{server}{$server}{disk}{xml}) = $anvil->Storage->read_file({
debug => $debug,
password => $password,
port => $port,
remote_user => $remote_user,
target => $target,
force_read => 1,
file => $anvil->data->{path}{directories}{shared}{definitions}."/".$server.".xml",
})
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"server::${server}::disk::xml" => $anvil->data->{server}{$server}{disk}{xml},
}});
return(0);
}
# =head3
#
# Private Functions;
#
# =cut
#############################################################################################################
# Private functions #
#############################################################################################################

@ -98,9 +98,6 @@ my $anvil = Anvil::Tools->new();
$anvil->Log->level({set => 2});
$anvil->Log->secure({set => 1});
$anvil->DRBD->get_status({debug => 2});
die;
### Read or Set the environment variables
# This is the name of the server we're managing. # Example values:
$anvil->data->{environment}{OCF_RESKEY_name} = defined $ENV{OCF_RESKEY_name} ? $ENV{OCF_RESKEY_name} : ""; # srv01-c7
@ -142,7 +139,6 @@ if ((not $anvil->data->{switches}{metadaata}) and (not $anvil->data->{switches}{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 3, key => "log_0298"});
}
### TEST: to be removed later
if ($anvil->data->{switches}{test1})
{
@ -317,110 +313,6 @@ sub start_server
my $server = $anvil->data->{environment}{OCF_RESKEY_name};
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0303", variables => { server => $server }});
# If the server is already here, we'll do nothing else.
my ($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{virsh}." list"});
if ($return_code)
{
# If this fails, we want to exit with OCF_ERR_CONFIGURED (6) so that pacemaker doesn't try to
# also start the server on another node, because we don't know the state of it here.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "log_0304", variables => { return_code => $return_code, output => $output }});
}
foreach my $line (split/\n/, $output)
{
$line =~ s/^\s+//;
$line =~ s/\s+$//;
$line =~ s/\s+/ /g;
if ($line =~ /^(\d+) $server (.*)$/)
{
my $state = $2;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
server => $server,
'state' => $state,
}});
# Make sure the server is shut down, if it is listed at all. Any other state is
# unexpected and needs to be sorted by a human.
if ($state ne "shut down")
{
# Abort
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0306", variables => { server => $server, 'state' => $state }});
$anvil->nice_exit({exit_code => 0});
}
last;
}
}
# We need to boot, validate everything.
validate_all($anvil);
# If we're still alive, we're ready to boot.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0305", variables => { server => $server }});
my $definition_file = $anvil->data->{path}{directories}{shared}{definitions}."/".$server.".xml";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { definition_file => $definition_file }});
$return_code = undef;
$output = undef;
($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{virsh}." create $definition_file"});
if ($return_code)
{
# If this fails, we want to exit with OCF_ERR_CONFIGURED (6) so that pacemaker doesn't try to
# also start the server on another node, because we don't know the state of it here.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0307", variables => {
server => $server,
return_code => $return_code,
output => $output,
}});
$anvil->nice_exit({exit_code => 6});
}
# Verify that it started.
sleep 2;
$return_code = undef;
$output = undef;
($output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{virsh}." list"});
if ($return_code)
{
# If this fails, we want to exit with OCF_ERR_CONFIGURED (6) so that pacemaker doesn't try to
# also start the server on another node, because we don't know the state of it here.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0308", variables => {
server => $server,
return_code => $return_code,
output => $output,
}});
$anvil->nice_exit({exit_code => 6});
}
foreach my $line (split/\n/, $output)
{
$line =~ s/^\s+//;
$line =~ s/\s+$//;
$line =~ s/\s+/ /g;
if ($line =~ /^(\d+) $server (.*)$/)
{
my $state = $2;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
server => $server,
'state' => $state,
}});
if ($state eq "running")
{
# Success!
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0309", variables => { server => $server }});
$anvil->nice_exit({exit_code => 0});
}
else
{
# WTF?
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0310", variables => { server => $server, 'state' => $state }});
$anvil->nice_exit({exit_code => 6});
}
last;
}
}
# If we're still alive, then we didn't see the server in the list of running servers, which is really weird.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0311", variables => { server => $server }});
@ -1451,22 +1343,18 @@ sub validate_storage_drbd
}
# Now read in the status of the drbd devices
$return_code = undef;
(my $status_json, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{drbdsetup}." status --json"});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
status_json => $status_json,
return_code => $return_code,
json_length => length($status_json),
}});
if ($return_code)
my $host = $anvil->_short_hostname;
$anvil->DRBD->get_status({debug => 2});
if ($anvil->data->{drbd}{status}{$host}{return_code})
{
# Something went wrong.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0377", variables => {
return_code => $return_code,
return_code => $anvil->data->{drbd}{status}{$host}{return_code},
status_json => $status_json,
}});
$anvil->nice_exit({exit_code => 1});
}
die;
# If DRBD is not up, the returned JSON output will not actually exist.
if (($status_json =~ /No currently configured DRBD found/si) or (not check_drbd_status($anvil, $status_json)))
@ -1535,88 +1423,6 @@ sub validate_storage_drbd
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { status_json => $status_json }});
check_drbd_status($anvil, $status_json);
### NOTE: The checks below might no longer be needed.
=cut
# Make sure I saw all disks.
my $check_again = 0;
foreach my $device_path (sort {$a cmp $b} keys %{$anvil->data->{server}{disks}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
device_path => $device_path,
"server::disks::${device_path}" => $anvil->data->{server}{disks}{$device_path},
}});
if ($anvil->data->{server}{disks}{$device_path} eq "check")
{
# Failed to see it, see if we can bring it up.
$check_again = 1;
my $resource = $anvil->data->{device_path}{$device_path}{resource};
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0384", variables => {
resource => $resource,
device_path => $device_path,
}});
#manage_drbd_resource($anvil, "up", $resource);
(my $drbdadm_output, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{drbdadm}." up $resource"});
if ($return_code)
{
# Something went wrong.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0380", variables => {
resource => $resource,
return_code => $return_code,
drbdadm_output => $drbdadm_output,
}});
$anvil->nice_exit({exit_code => 1});
}
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { check_again => $check_again }});
if ($check_again)
{
# Give the resource a few seconds to start.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0381"});
sleep 3;
# Check again.
$return_code = undef;
$status_json = undef;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0385"});
($status_json, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{drbdsetup}." status --json"});
if ($return_code)
{
# Something went wrong.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0382", variables => {
return_code => $return_code,
status_json => $status_json,
}});
$anvil->nice_exit({exit_code => 1});
}
# Check again.
check_drbd_status($anvil, $status_json);
}
}
# Do I need to check again?
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {check_again => $check_again }});
if ($check_again)
{
foreach my $device_path (sort {$a cmp $b} keys %{$anvil->data->{server}{disks}})
{
if ($anvil->data->{server}{disks}{$device_path} eq "check")
{
# Failed.
my $resource = $anvil->data->{device_path}{$device_path}{resource};
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "log_0386", variables => {
resource => $resource,
device_path => $device_path,
}});
$anvil->nice_exit({exit_code => 1});
}
}
}
=cut
### TODO: Finish this, whatever this was going to be...
# If I am about to push a server off, we need to make sure the peer is UpToDate
if ($anvil->data->{switches}{migrate_to})

Loading…
Cancel
Save