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 strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
use base 'DBIx::Class::Core';
|
use base 'Netdisco::DB::Result::Node';
|
||||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
|
||||||
|
|
||||||
|
__PACKAGE__->load_components('Helper::Row::SubClass');
|
||||||
|
__PACKAGE__->subclass;
|
||||||
|
|
||||||
|
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||||
__PACKAGE__->table("active_node");
|
__PACKAGE__->table("active_node");
|
||||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||||
__PACKAGE__->result_source_instance->view_definition(
|
__PACKAGE__->result_source_instance->view_definition(q{
|
||||||
'SELECT * FROM node WHERE active'
|
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') }
|
|
||||||
|
|
||||||
1;
|
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');
|
__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
|
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
|
=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.
|
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
|
=back
|
||||||
|
|
||||||
=cut
|
=cut
|
||||||
@@ -102,25 +104,13 @@ __PACKAGE__->has_many( nodes => 'Netdisco::DB::Result::Node',
|
|||||||
{ join_type => 'LEFT' },
|
{ join_type => 'LEFT' },
|
||||||
);
|
);
|
||||||
|
|
||||||
=head2 active_nodes
|
__PACKAGE__->has_many( nodes_with_age => 'Netdisco::DB::Result::NodeWithAge',
|
||||||
|
{
|
||||||
Returns the set of I<active> Nodes whose MAC addresses are associated with
|
'foreign.switch' => 'self.ip',
|
||||||
this Device Port. See C<nodes()> to find all Nodes (active and inactive).
|
'foreign.port' => 'self.port',
|
||||||
|
},
|
||||||
=over 4
|
{ join_type => 'LEFT' },
|
||||||
|
);
|
||||||
=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( active_nodes => 'Netdisco::DB::Result::ActiveNode',
|
__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' },
|
{ 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
|
=head2 neighbor_alias
|
||||||
|
|
||||||
When a device port has an attached neighbor device, this relationship will
|
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') }
|
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;
|
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
|
=head2 with_vlan_count
|
||||||
|
|
||||||
This is a modifier for any C<search()> (including the helpers below) which
|
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
|
# get number of vlans on the port to control whether to list them or not
|
||||||
$set = $set->with_vlan_count if param('c_vmember');
|
$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');
|
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({}, {
|
$set = $set->search_rs({}, {
|
||||||
prefetch => [{$nodes_name => 'ips'}, {neighbor_alias => 'device'}],
|
prefetch => [{$nodes_name => 'ips'}, {neighbor_alias => 'device'}],
|
||||||
}) if param('c_connected');
|
}) 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
|
# sort, and filter by free ports
|
||||||
# the filter could be in the template but here allows a 'no records' msg
|
# the filter could be in the template but here allows a 'no records' msg
|
||||||
my $results = [ sort { &netdisco::sort_port($a->port, $b->port) }
|
my $results = [ sort { &netdisco::sort_port($a->port, $b->port) }
|
||||||
|
|||||||
Reference in New Issue
Block a user