# -*-perl-*-

# $Id: AllHosts.plm,v 1.10 2008/04/10 19:51:48 thomas Exp $

# Structural component for code to parse all hosts in the universe.


=head1 NAME

What::AllHosts -- build ancillary databases from What data


=head1 DESCRIPTION

The B<AllHosts> module coordinates activities that involve scanning
all hosts known to B<What>, and performing some set of analysis on
each host's data.


=head1 SYOPSIS

=over 4

=item $res = What::AllHosts->do ($what, [@options], [@modules], [@hosts]);

B<$what> is the global what object

B<@options> is an array of options of the form
B<Sub-module>:B<Attribute>=B<Value> (see B<OPTION PASSING> below).

B<@modules> is a list of modules over which to iterate (the default is
all modules known to B<What>).

B<@hosts> is a list of hosts over which to iterate (the default is all
hosts known to B<What>).

=item @modules = What::AllHosts->getmodules();

Returns a list of all modules known to what.

=back

=head1 OPTION PASSING

To avoid conflicts between B<AllHosts> sub-modules, option passing to
B<AllHosts> sub-modules uses a simplified
B<Sub-module>:B<Attribute>=B<Value> interface.  Only those attributes
corresponding to a given sub-module will be passed to the sub-module
in the b<begin> method.  If options will be required later, it is up
to the sub-module to store its options for later reference.

One option, named b<allhosts>, is common to all sub-modules.  It is a
boolean, which is set to 0 if AllHosts was passed an explicit list of
hosts to parse.  If no hosts are given (meaning AllHosts should
iterate over all hosts known to b<What>), then b<allhosts> will be set
to 1.


=head1 SUBMODULE METHODS

AllHosts modules are required to export the following methods:

=over 4

=item begin

$ref = What::AllHosts::b<sub-module>->begin ($what, $options)

This method should initialize any data required for the sub-module.
It must return a blessed scalar (customarily a hash reference, but not
required) to access the b<build> and b<finish> methods of the
sub-module.

b<$what> is the master b<What> data object.

b<$options> is a hash of options for the sub-module.

=item build

$result = $ref->build ($hostref, $config, $inctree)

This method will be called once for each host known to b<AllHosts>.
It processes the data for the host.  b<$name> is the name of the host
(without any .xml suffix), and b<$what> is the configuration data for
the host.

B<$hostref> is a hash reference, containing the following csdb data:

=over 4

=item cpuname

The name of the computer

=item os

The csdb operating system

=item type

The monitoring type.

=item frequency

The monitoring notification frequency (almost never used).

=back

B<$config> is a B<What> object containing the configuration
for the host.

B<$inctree> is the include tree obtained while loading B<$config>.


=item finish

$res = $ref->finish ()

This method will be called after all hosts have been processed.  If
the method needs to write any global configuration data, it should be
done at this time.

=back

After b<AllHosts> calls the b<finish> method for each sub-module,
AllHosts will delete the modules (and typically exit).

=head1 SEE ALSO

What::Command::allhosts
What::AllHosts::BuildDepDB
What::AllHosts::StashedTickets
What::AllHosts::Nagios
What::AllHosts::BuildCsdbData

=cut

package  What::AllHosts;

use strict;
use FileHandle;
use XML::Parser;
use Data::Dumper;
use File::stat;
use File::MkTemp;
use Sys::Hostname;
use Time::localtime;
use Net::hostent;

use What::Csdb;
use What::MachineStatus;
use What::OsMap;
use base 'What';

###############################################################################
#
# This is the main function of the AllHosts subsystem.  It calls the
# 'begin' methods of all the AllHosts modules, iterates over all
# hosts, calling the 'build' method for each subsystem for each hosts,
# and finally calling the 'finish' method for each subsystem.

sub do {
  my $ref = shift;
  my $what = shift;
  my $options = shift;
  my $modules = shift;
  my $hosts = shift;

  my ($href, $submodule, $config, $inctree, $host, $h, $stat, $osmap);
  my $self = bless {}, ref($ref) || $ref;

  # Stash the host list
  $self->{hosts} = [];
  if (defined ($hosts) && @$hosts) {
    $self->{hosts} = $hosts;
  } else {
    $self->{allhosts} = 1;
  }

  # Stash the module list
  $self->{modules} = [];
  defined ($modules) && @$modules and $self->{modules} = $modules;

  # Stash the sub-module arguments
  $self->{options} = $options;

  # Load the sub-modules and initialize
  $self->initialize_modules ($what);

  if ($options->{delete}) {
    foreach $host (@$hosts) {
      $h = gethost ($host);
      if (!defined $h) {
	if (!$what->yesno (0, "$host not found in dns.  Really delete $host")) {
	  next;
	}
      } elsif ($h->name ne $host) {
	if ($what->yesno (1, "Delete " . $h->name)) {
	  $host = $h->name;
	} elsif (!$what->yesno (0, "Really delete $host" )) {
	  next;
	}
      }
      # Do the delete.  Special ordering doesn't apply to deletes.
      foreach $submodule (sort keys %{$self->{submodules}}) {
	$self->{submodules}->{$submodule}->do_delete ($host, $what);
      }
    }
    return;
  }

  # [CSL #349862] /s/what/xml/csdb is empty
  # Bail if csdb doesn't give us enough, in case sybase is down
  $hosts = $self->gethosts;
  return unless defined ($hosts) && @$hosts;
  if ($self->{allhosts}) {
    return unless @$hosts > 99; # Require 100 to do deletes
  }

  foreach $href (@$hosts) {
    $what->debug (1, "Processing $href->{cpuname}...\n");

    # Make sure the MachineStatus bits are there
    if (!defined $href->{MachineStatus}) {
      $stat = new What::MachineStatus unless defined $stat;
      $href->{MachineStatus} = $stat->find ($href->{status})
	if defined $stat;
    }

    # Make sure the OsMap attributes are there
    if (!defined $href->{OsMap}) {
      $osmap = new What::OsMap unless defined $osmap;
      $href->{OsMap} = $osmap->get_attrs (csdb=>$href->{os})
	if defined $osmap;
    }

    # <Chicken-and-egg>
    # Always do the Csdb submodule first, so that if the host doesn't
    # exist, we can create it before calling the other modules,
    # so that it can be loaded.
    # Fortunately, the Csdb sub-module doesn't use $config or $inctree.
    # </Chicken-and-egg>
    if (defined $self->{submodules}->{Csdb}) {
      next unless $self->{submodules}->{Csdb}->build ($href, undef, undef);
    }
    ($config, $inctree) = $self->readconfig ($what, $href->{cpuname});
    defined ($config) or next;
    foreach $submodule (sort keys %{$self->{submodules}}) {
      ($submodule eq 'Csdb') and next;
      $self->{submodules}->{$submodule}->build ($href, $config, $inctree);
    }
  }

  foreach $submodule (sort keys %{$self->{submodules}}) {
    $self->{submodules}->{$submodule}->finish ();
  }

  return;
}


###############################################################################
# Return a list of modules

sub getmodules {
  my $self = shift;

  my (@modules, $dh, $de);
  defined ($dh = new DirHandle '/s/what-0.1.31/lib/What/AllHosts')
    or die "Failed to open /s/what-0.1.31/lib/What/AllHosts\n";
  while (defined ($de = $dh->read)) {
    $de =~ s/\.pm$// or next;
    push @modules, $de;
  }
  @modules;
}


###############################################################################
# Read a host xml file, and return the configuration and the include tree.

sub readconfig {
  my $self = shift;
  my $what = shift;
  my $hostname = shift;

  my ($inctree);
  my $config = new What;
  # Copy the include path from $what
  my $include_path = $what->include_path();
  # <hack>
  # If I rewrite the csdb file, I need to read from the RW path in afs
  # to get the newly written file.  If I don't "adjust" the search
  # path, I'll get the RO csdb file instead.  (sigh)
  foreach (@$include_path) {
    s;^/s/what-0.1.31/xml;/afs/.cs/s/what-0.1.31/xml;;
    s;^/afs/cs;/afs/.cs;;
  }
  # </hack>
  $config->include_path (0, @$include_path);
  my $section = $config->find_section ('default', 1);
  $inctree = $section->readhost ($config, name => $hostname);
  defined ($inctree) or return (undef, undef);
  return ($config, $inctree);
}


###############################################################################
# Iterate over all the hosts known to csdb

sub gethosts {
  my $self = shift;

  my ($csdb, $hosts, $hname, $h) = (undef, [], undef, undef);
  $csdb = new What::Csdb;

  # See if the user gave a host list
  if (@{$self->{hosts}}) {
    foreach (@{$self->{hosts}}) {
      $hname = $_;
      if (!$self->{options}->{nodns}) {
	$h = gethostbyname ($_);
	$hname = $h->name if defined $h;
      }
      ($hname eq $_) or $self->debug(2, "Using $hname for $_...\n");
      my $href;
      (defined ($href = $csdb->host_lookup ($hname))) and push @$hosts, $href;
    }
    return $hosts;
  }

  # Do all hosts
  $csdb->start_hostlist;
  while (defined (my $href = $csdb->next_host)) {
    push @$hosts, $href;
  }
  $hosts;
}

###############################################################################
# Skip any modules with errors, but do the others

sub initialize_modules {
  my $self = shift;
  my $what = shift;

  my ($module, $allhosts, @modules);

  $allhosts = 1;
  @{$self->{hosts}} and $allhosts = 0;

  if (@{$self->{modules}}) {
    # Use the given list
    @modules = @{$self->{modules}};
  } else {
    @modules = $self->getmodules;
  }

  foreach $module (sort @modules) {
    eval "require What::AllHosts::$module;";
    if ($@) {
      warn "Failed to require What::AllHosts::$module!!\n$@\n";
      next;
    }

    # Now initialize the module
    defined ($self->{options}->{$module}) or $self->{options}->{$module} = {};
    $self->{options}->{$module}->{allhosts} = $allhosts;
    eval "\$self->{submodules}->{$module} = What::AllHosts::$module->begin (\$what, \$self->{options}->{$module});";
    if ($@) {
      warn "Failed to initialize What::AllHosts::$module!!\n$@\n";
      delete $self->{submodules}->{$module};
    }
  }
}

###############################################################################
# Default sub-module do_delete method
# Can't remove old data -- don't know the directories

sub do_delete {
  my $self = shift;
  my $host = shift;
  my $what = shift;

  $what->debug (1, "Sorry, I don't delete " . ref ($self) . " data ($host).\n");
}

###############################################################################

1;
