App-Netdisco

 view release on metacpan or  search on metacpan

xt/175-discover-neighbor-columns.t  view on Meta::CPAN

use App::Netdisco::Backend::Job;
use App::Netdisco::Transport::SNMP ();
use App::Netdisco::Worker::Plugin::Discover::Neighbors ();
use DBIx::Class::SQLMaker;

use Try::Tiny;
use Dancer qw/:moose :script !pass/;

# The Neighbors worker runs for real with SNMP stubbed. Each DevicePort
# search-and-update is rendered to SQL by DBIx::Class::SQLMaker and recorded,
# so the WHERE that decides which rows are written is checked without a
# database. Ports no longer lose remote_* and is_uplink to a delete and
# reinsert each discover, so Neighbors clears them itself.

my $CONFIG = config();
$CONFIG->{logger} = 'console';
$CONFIG->{log} = ($ENV{'DANCER_DEBUG'} ? 'debug' : 'error');
Dancer::Logger->init('console', $CONFIG);

{
  package MyWorker;
  use Moo;
  with 'App::Netdisco::Worker::Runner';
}

config->{'device_auth'} = [{driver => 'snmp'}];
config->{'enable_incomplete_walk_protection'} = 1;
config->{'queue_neighbors'} = 1;
config->{'discover_neighbors'} = 1;

package FakeSNMP;
our $AUTOLOAD;
sub new { my ($class, %vals) = @_; return bless { walk_failures => [], %vals }, $class; }
sub walk_failures { $_[0]{walk_failures} }
sub walk_status { $_[0]{walk_status}{$_[1]} }
sub AUTOLOAD {
  my $self = shift;
  (my $method = $AUTOLOAD) =~ s/.*:://;
  return if $method eq 'DESTROY';
  if (my $failure = delete $self->{fail_on_read}{$method}) {
    push @{ $self->{walk_failures} }, $failure;
    $self->{walk_status}{$method} = $failure;
  }
  # SNMP::Info returns an empty list, not undef, for a method the device does
  # not support, so a caller in list context sees no value at all
  return unless exists $self->{$method};
  return $self->{$method};
}

package FakePortRow;
sub new { my ($class, %cols) = @_; return bless { manual_topo => 0, %cols }, $class; }
sub port { $_[0]{port} }
sub manual_topo { $_[0]{manual_topo} }
sub slave_of { $_[0]{slave_of} }
sub is_master { $_[0]{is_master} }
sub remote_port { $_[0]{remote_port} }
sub remote_ip { $_[0]{remote_ip} }
sub is_uplink { $_[0]{is_uplink} }
sub discard_changes { $_[0] }
sub store_column { my ($self, $col, $value) = @_; $self->{$col} = $value }
sub update {
  my ($self, $values) = @_;
  push @main::row_updates,
    [ $self->{owner}, $self->{port}, $values, $self, { map {($_ => $self->{$_})} keys %$values } ];
  push @main::update_depths, [ $self->{port}, $main::txn_depth ]
    if ref $values->{manual_topo} and ${ $values->{manual_topo} } eq 'true';
  foreach my $col (keys %$values) {
    my $value = $values->{$col};
    $self->{$col} = ref $value ? (${$value} eq 'true' ? 1 : 0) : $value;
  }
  return $self;
}

package FakePorts;
sub new { my ($class, @rows) = @_; return bless { rows => [@rows] }, $class; }
sub all { @{ $_[0]{rows} } }
sub reset { $_[0] }
sub single { my ($self, $cond) = @_; return (grep { $_->port eq $cond->{port} } $self->all)[0] }
sub get_column { $_[0] }
sub as_query { \['(SELECT port FROM device_port WHERE ip = ?)'] }

package FakeDevicePortRS;
sub search { my ($self, $where) = @_; return bless { where => $where }, ref $self }
sub single {
  my ($self, $cond) = @_;
  return (grep { $_->{owner} eq $cond->{ip} and $_->port eq $cond->{port} } @main::port_rows)[0];
}
sub update {
  my ($self, $set) = @_;
  my ($sql, @bind) = DBIx::Class::SQLMaker->new->update('device_port', $set, $self->{where});
  push @main::port_updates, { sql => $sql, bind => [@bind], set => $set, depth => $main::txn_depth };
  return 0;
}

package FakeTopologyRS;
sub search { $_[0] }
sub delete { push @main::topology_delete_depths, $main::txn_depth; 0 }
sub next { shift @{ $_[0]{links} } }

package FakeLink;
sub dev1 { $_[0]{dev1} } sub port1 { $_[0]{port1} }
sub dev2 { $_[0]{dev2} } sub port2 { $_[0]{port2} }

package FakePeer;
sub ip { $_[0]{ip} }
sub in_storage { 1 }
sub ports { $_[0]{ports} }

package FakeSchema;
# rolls back the row updates made inside a block that dies, as Netdisco's
# auto_savepoint makes a nested txn_do do, and counts the nesting so a test
# can tell a top-level transaction from a savepoint
sub txn_do {
  my ($self, $code, @args) = @_;
  local $main::txn_depth = $main::txn_depth + 1;
  my $mark = scalar @main::row_updates;
  my @result = eval { $code->(@args) };
  if (my $error = $@) {
    foreach my $undo (reverse splice @main::row_updates, $mark) {
      my ($row, $old) = @{$undo}[3, 4];
      $row->{$_} = $old->{$_} for keys %$old;
    }
    die $error;
  }
  return @result;
}
sub resultset {
  my ($self, $name) = @_;
  return bless({}, 'FakeDevicePortRS') if $name eq 'DevicePort';
  return bless({ links => [ map { bless {%$_}, 'FakeLink' } @main::links ] }, 'FakeTopologyRS')
    if $name eq 'Topology';
  die "unexpected resultset $name";
}

package main;

our (@row_updates, @port_updates, @links, @warnings, @port_rows,
  @update_depths, @topology_delete_depths);
our $txn_depth = 0;

my %device_cols = (
  ip => '192.0.2.1', name => 'switch1', snmp_class => 'SNMP::Info::Layer2',
  custom_fields => '{}', snmp_ver => 2,
);

my %base_snmp = (
  has_topo   => 1,
  interfaces => { 1 => 'Gi1/1', 2 => 'Gi1/2', 3 => 'Gi1/3' },



( run in 1.013 second using v1.01-cache-2.11-cpan-145b269f2f3 )