more performant implementation of active/age node search
This commit is contained in:
@@ -4,145 +4,16 @@ package Netdisco::DB::Result::ActiveNode;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
use base 'Netdisco::DB::Result::Node';
|
||||
|
||||
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||
__PACKAGE__->subclass;
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table("active_node");
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(
|
||||
'SELECT * FROM node WHERE active'
|
||||
);
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"mac",
|
||||
{ data_type => "macaddr", is_nullable => 0 },
|
||||
"switch",
|
||||
{ data_type => "inet", is_nullable => 0 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 0 },
|
||||
"active",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
"oui",
|
||||
{ data_type => "varchar", is_nullable => 1, size => 8 },
|
||||
"time_first",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"time_recent",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
"time_last",
|
||||
{
|
||||
data_type => "timestamp",
|
||||
default_value => \"current_timestamp",
|
||||
is_nullable => 1,
|
||||
original => { default_value => \"now()" },
|
||||
},
|
||||
);
|
||||
__PACKAGE__->set_primary_key("mac", "switch", "port");
|
||||
|
||||
|
||||
=head1 RELATIONSHIPS
|
||||
|
||||
=head2 device
|
||||
|
||||
Returns the single C<device> to which this Node entry was associated at the
|
||||
time of discovery.
|
||||
|
||||
The JOIN is of type LEFT, in case the C<device> is no longer present in the
|
||||
database but the relation is being used in C<search()>.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'Netdisco::DB::Result::Device',
|
||||
{ 'foreign.ip' => 'self.switch' }, { join_type => 'LEFT' } );
|
||||
|
||||
=head2 device_port
|
||||
|
||||
Returns the single C<device_port> to which this Node entry was associated at
|
||||
the time of discovery.
|
||||
|
||||
The JOIN is of type LEFT, in case the C<device> is no longer present in the
|
||||
database but the relation is being used in C<search()>.
|
||||
|
||||
=cut
|
||||
|
||||
# device port may have been deleted (reconfigured modules?) but node remains
|
||||
__PACKAGE__->belongs_to( device_port => 'Netdisco::DB::Result::DevicePort',
|
||||
{ 'foreign.ip' => 'self.switch', 'foreign.port' => 'self.port' },
|
||||
{ join_type => 'LEFT' }
|
||||
);
|
||||
|
||||
=head2 ips
|
||||
|
||||
Returns the set of C<node_ip> entries associated with this Node. That is, the
|
||||
IP addresses which this MAC address was hosting at the time of discovery.
|
||||
|
||||
Note that the Active status of the returned IP entries will all be the same as
|
||||
the current Node's.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->has_many( ips => 'Netdisco::DB::Result::NodeIp',
|
||||
{ 'foreign.mac' => 'self.mac', 'foreign.active' => 'self.active' } );
|
||||
|
||||
=head2 oui
|
||||
|
||||
Returns the C<oui> table entry matching this Node. You can then join on this
|
||||
relation and retrieve the Company name from the related table.
|
||||
|
||||
The JOIN is of type LEFT, in case the OUI table has not been populated.
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->belongs_to( oui => 'Netdisco::DB::Result::Oui', 'oui',
|
||||
{ join_type => 'LEFT' } );
|
||||
|
||||
=head1 ADDITIONAL COLUMNS
|
||||
|
||||
=head2 time_first_stamp
|
||||
|
||||
Formatted version of the C<time_first> field, accurate to the minute.
|
||||
|
||||
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
|
||||
between the date stamp and time stamp. That is:
|
||||
|
||||
2012-02-06 12:49
|
||||
|
||||
=cut
|
||||
|
||||
sub time_first_stamp { return (shift)->get_column('time_first_stamp') }
|
||||
|
||||
=head2 time_last_stamp
|
||||
|
||||
Formatted version of the C<time_last> field, accurate to the minute.
|
||||
|
||||
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
|
||||
between the date stamp and time stamp. That is:
|
||||
|
||||
2012-02-06 12:49
|
||||
|
||||
=cut
|
||||
|
||||
sub time_last_stamp { return (shift)->get_column('time_last_stamp') }
|
||||
|
||||
=head2 time_last_age
|
||||
|
||||
Formatted version of the C<time_last> field, accurate to the minute.
|
||||
|
||||
The format is in "X days/months/years" style, similar to:
|
||||
|
||||
1 year 4 months 05:46:00
|
||||
|
||||
=cut
|
||||
|
||||
sub time_last_age { return (shift)->get_column('time_last_age') }
|
||||
__PACKAGE__->result_source_instance->view_definition(q{
|
||||
SELECT * FROM node WHERE active
|
||||
});
|
||||
|
||||
1;
|
||||
|
||||
27
Netdisco/lib/Netdisco/DB/Result/ActiveNodeWithAge.pm
Normal file
27
Netdisco/lib/Netdisco/DB/Result/ActiveNodeWithAge.pm
Normal file
@@ -0,0 +1,27 @@
|
||||
use utf8;
|
||||
package Netdisco::DB::Result::ActiveNodeWithAge;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'Netdisco::DB::Result::ActiveNode';
|
||||
|
||||
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||
__PACKAGE__->subclass;
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table("active_node_with_age");
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(q{
|
||||
SELECT *,
|
||||
replace(age( date_trunc( 'minute', time_last + interval '30 second' ) ) ::text, 'mon', 'month')
|
||||
AS time_last_age
|
||||
FROM node WHERE active
|
||||
});
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"time_last_age",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
|
||||
1;
|
||||
@@ -74,10 +74,17 @@ Returns the Device table entry to which the given Port is related.
|
||||
|
||||
__PACKAGE__->belongs_to( device => 'Netdisco::DB::Result::Device', 'ip');
|
||||
|
||||
=head2 nodes
|
||||
=head2 nodes / active_nodes / nodes_with_age / active_nodes_with_age
|
||||
|
||||
Returns the set of Nodes whose MAC addresses are associated with this Device
|
||||
Port. See C<active_nodes()> to find only the active Nodes, instead.
|
||||
Port.
|
||||
|
||||
The C<active> variants return only the subset of nodes currently in the switch
|
||||
MAC address table, that is the active ones.
|
||||
|
||||
The C<with_age> variants add an additional column C<time_last_age>, a
|
||||
preformatted value for the Node's C<time_last> field, which reads as "X
|
||||
days/weeks/months/years".
|
||||
|
||||
=over 4
|
||||
|
||||
@@ -85,11 +92,6 @@ Port. See C<active_nodes()> to find only the active Nodes, instead.
|
||||
|
||||
Rows returned are sorted by the Node MAC address.
|
||||
|
||||
=item *
|
||||
|
||||
The additional column C<time_last_age> is a preformatted value for the Node's
|
||||
C<time_last> field, which reads as "X days/weeks/months/years".
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -102,25 +104,13 @@ __PACKAGE__->has_many( nodes => 'Netdisco::DB::Result::Node',
|
||||
{ join_type => 'LEFT' },
|
||||
);
|
||||
|
||||
=head2 active_nodes
|
||||
|
||||
Returns the set of I<active> Nodes whose MAC addresses are associated with
|
||||
this Device Port. See C<nodes()> to find all Nodes (active and inactive).
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
Rows returned are sorted by the Node MAC address.
|
||||
|
||||
=item *
|
||||
|
||||
The additional column C<time_last_age> is a preformatted value for the Node's
|
||||
C<time_last> field, which reads as "X days/weeks/months/years".
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
__PACKAGE__->has_many( nodes_with_age => 'Netdisco::DB::Result::NodeWithAge',
|
||||
{
|
||||
'foreign.switch' => 'self.ip',
|
||||
'foreign.port' => 'self.port',
|
||||
},
|
||||
{ join_type => 'LEFT' },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many( active_nodes => 'Netdisco::DB::Result::ActiveNode',
|
||||
{
|
||||
@@ -130,6 +120,14 @@ __PACKAGE__->has_many( active_nodes => 'Netdisco::DB::Result::ActiveNode',
|
||||
{ join_type => 'LEFT' },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many( active_nodes_with_age => 'Netdisco::DB::Result::ActiveNodeWithAge',
|
||||
{
|
||||
'foreign.switch' => 'self.ip',
|
||||
'foreign.port' => 'self.port',
|
||||
},
|
||||
{ join_type => 'LEFT' },
|
||||
);
|
||||
|
||||
=head2 neighbor_alias
|
||||
|
||||
When a device port has an attached neighbor device, this relationship will
|
||||
|
||||
@@ -132,16 +132,4 @@ between the date stamp and time stamp. That is:
|
||||
|
||||
sub time_last_stamp { return (shift)->get_column('time_last_stamp') }
|
||||
|
||||
=head2 time_last_age
|
||||
|
||||
Formatted version of the C<time_last> field, accurate to the minute.
|
||||
|
||||
The format is in "X days/months/years" style, similar to:
|
||||
|
||||
1 year 4 months 05:46:00
|
||||
|
||||
=cut
|
||||
|
||||
sub time_last_age { return (shift)->get_column('time_last_age') }
|
||||
|
||||
1;
|
||||
|
||||
27
Netdisco/lib/Netdisco/DB/Result/NodeWithAge.pm
Normal file
27
Netdisco/lib/Netdisco/DB/Result/NodeWithAge.pm
Normal file
@@ -0,0 +1,27 @@
|
||||
use utf8;
|
||||
package Netdisco::DB::Result::NodeWithAge;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'Netdisco::DB::Result::Node';
|
||||
|
||||
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||
__PACKAGE__->subclass;
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table("node_with_age");
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
__PACKAGE__->result_source_instance->view_definition(q{
|
||||
SELECT *,
|
||||
replace(age( date_trunc( 'minute', time_last + interval '30 second' ) ) ::text, 'mon', 'month')
|
||||
AS time_last_age
|
||||
FROM node
|
||||
});
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"time_last_age",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
);
|
||||
|
||||
1;
|
||||
@@ -1,64 +0,0 @@
|
||||
package Netdisco::DB::ResultSet::ActiveNode;
|
||||
use base 'DBIx::Class::ResultSet';
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
|
||||
=head1 search_by_mac( \%cond, \%attrs? )
|
||||
|
||||
my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1});
|
||||
|
||||
Like C<search()>, this returns a ResultSet of matching rows from the Node
|
||||
table.
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
The C<cond> parameter must be a hashref containing a key C<mac> with
|
||||
the value to search for.
|
||||
|
||||
=item *
|
||||
|
||||
Results are ordered by time last seen.
|
||||
|
||||
=item *
|
||||
|
||||
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
|
||||
preformatted timestamps of the C<time_first> and C<time_last> fields.
|
||||
|
||||
=item *
|
||||
|
||||
A JOIN is performed on the Device table and the Device C<dns> column
|
||||
prefetched.
|
||||
|
||||
=back
|
||||
|
||||
To limit results only to active nodes, set C<< {active => 1} >> in C<cond>.
|
||||
|
||||
=cut
|
||||
|
||||
sub search_by_mac {
|
||||
my ($rs, $cond, $attrs) = @_;
|
||||
|
||||
die "mac address required for search_by_mac\n"
|
||||
if ref {} ne ref $cond or !exists $cond->{mac};
|
||||
|
||||
$cond->{'me.mac'} = delete $cond->{mac};
|
||||
$attrs ||= {};
|
||||
|
||||
return $rs
|
||||
->search_rs({}, {
|
||||
order_by => {'-desc' => 'time_last'},
|
||||
'+columns' => [qw/ device.dns /],
|
||||
'+select' => [
|
||||
\"to_char(time_first, 'YYYY-MM-DD HH24:MI')",
|
||||
\"to_char(time_last, 'YYYY-MM-DD HH24:MI')",
|
||||
],
|
||||
'+as' => [qw/ time_first_stamp time_last_stamp /],
|
||||
join => 'device',
|
||||
})
|
||||
->search($cond, $attrs);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -37,35 +37,6 @@ sub with_times {
|
||||
});
|
||||
}
|
||||
|
||||
=head2 with_node_age
|
||||
|
||||
This is a modifier for any C<search()> (including the helpers below) which
|
||||
will add the following additional synthesized columns to the result set:
|
||||
|
||||
=over 4
|
||||
|
||||
=item $nodes.time_last_age
|
||||
|
||||
=back
|
||||
|
||||
You can pass in the table alias for the Nodes relation, which defaults to
|
||||
C<nodes>.
|
||||
|
||||
=cut
|
||||
|
||||
sub with_node_age {
|
||||
my ($rs, $alias) = @_;
|
||||
$alias ||= 'nodes';
|
||||
|
||||
return $rs
|
||||
->search_rs({},
|
||||
{
|
||||
'+select' =>
|
||||
[\"replace(age(date_trunc('minute', $alias.time_last + interval '30 second'))::text, 'mon', 'month')"],
|
||||
'+as' => [ "$alias.time_last_age" ],
|
||||
});
|
||||
}
|
||||
|
||||
=head2 with_vlan_count
|
||||
|
||||
This is a modifier for any C<search()> (including the helpers below) which
|
||||
|
||||
@@ -99,16 +99,15 @@ ajax '/ajax/content/device/ports' => sub {
|
||||
# get number of vlans on the port to control whether to list them or not
|
||||
$set = $set->with_vlan_count if param('c_vmember');
|
||||
|
||||
# retrieve active/all connected nodes, and device, if asked for
|
||||
# what kind of nodes are we interested in?
|
||||
my $nodes_name = (param('n_archived') ? 'nodes' : 'active_nodes');
|
||||
$nodes_name .= '_with_age' if param('c_connected') and param('n_age');
|
||||
|
||||
# retrieve active/all connected nodes, and device, if asked for
|
||||
$set = $set->search_rs({}, {
|
||||
prefetch => [{$nodes_name => 'ips'}, {neighbor_alias => 'device'}],
|
||||
}) if param('c_connected');
|
||||
|
||||
# add constructed node age col if requested (and showing connected)
|
||||
$set = $set->with_node_age($nodes_name)
|
||||
if param('c_connected') and param('n_age');
|
||||
|
||||
# sort, and filter by free ports
|
||||
# the filter could be in the template but here allows a 'no records' msg
|
||||
my $results = [ sort { &netdisco::sort_port($a->port, $b->port) }
|
||||
|
||||
Reference in New Issue
Block a user