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