From 3aeed20b78d3f198744d5b3e03be3c3964831ec7 Mon Sep 17 00:00:00 2001 From: Oliver Gorwits Date: Thu, 6 Aug 2020 21:25:01 +0100 Subject: [PATCH] #735 Slow web search for devices --- Changes | 6 +++ lib/App/Netdisco/DB/Result/Device.pm | 43 ++++++++++++++++++++ lib/App/Netdisco/DB/ResultSet/Device.pm | 52 ++++++++++++++++++++++--- 3 files changed, 95 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index 25874e71..8213ea11 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,9 @@ +2.046002 - 2020-08-?? + + [BUG FIXES] + + * #735 Slow web search for devices + 2.046001 - 2020-07-10 [ENHANCEMENTS] diff --git a/lib/App/Netdisco/DB/Result/Device.pm b/lib/App/Netdisco/DB/Result/Device.pm index a1511f25..dbfce7f8 100644 --- a/lib/App/Netdisco/DB/Result/Device.pm +++ b/lib/App/Netdisco/DB/Result/Device.pm @@ -94,6 +94,30 @@ all the interface IP aliases configured on the Device. __PACKAGE__->has_many( device_ips => 'App::Netdisco::DB::Result::DeviceIp', 'ip' ); +=head2 device_ips_by_address_or_name + +Returns rows from the C table which relate to this Device. That is, +all the interface IP aliases configured on the Device. However you probably +want to use the C ResultSet method instead, +so you can pass the MAC address part. + +=cut + +__PACKAGE__->has_many( device_ips_by_address_or_name => 'App::Netdisco::DB::Result::DeviceIp', + sub { + my $args = shift; + return { + "$args->{foreign_alias}.ip" => { -ident => "$args->{self_alias}.ip" }, + -or => [ + "$args->{foreign_alias}.dns" => { 'ilike', \'?' }, + "$args->{foreign_alias}.alias" => { '<<=', \'?' }, + "$args->{foreign_alias}.alias::text" => { 'ilike', \'?' }, + ], + }; + }, + { cascade_copy => 0, cascade_update => 0, cascade_delete => 0 } +); + =head2 vlans Returns the C entries for this Device. That is, the list of VLANs @@ -111,6 +135,25 @@ Returns the set of ports on this Device. __PACKAGE__->has_many( ports => 'App::Netdisco::DB::Result::DevicePort', 'ip' ); +=head2 ports_by_mac + +Returns the set of ports on this Device, filtered by MAC. However you probably +want to use the C ResultSet method instead, so you can pass the +MAC address part. + +=cut + +__PACKAGE__->has_many( ports_by_mac => 'App::Netdisco::DB::Result::DevicePort', + sub { + my $args = shift; + return { + "$args->{foreign_alias}.ip" => { -ident => "$args->{self_alias}.ip" }, + "$args->{foreign_alias}.mac::text" => { 'ilike', \'?' }, + }; + }, + { cascade_copy => 0, cascade_update => 0, cascade_delete => 0 } +); + =head2 modules Returns the set chassis modules on this Device. diff --git a/lib/App/Netdisco/DB/ResultSet/Device.pm b/lib/App/Netdisco/DB/ResultSet/Device.pm index 123c47a8..fc67d3d6 100644 --- a/lib/App/Netdisco/DB/ResultSet/Device.pm +++ b/lib/App/Netdisco/DB/ResultSet/Device.pm @@ -13,6 +13,43 @@ require Dancer::Logger; =head1 ADDITIONAL METHODS +=head2 device_ips_with_address_or_name( $address_or_name ) + +Returns a correlated subquery for the set of C entries for each +device. The IP alias or dns matches the supplied C, using +C. + +=cut + +sub device_ips_with_address_or_name { + my ($rs, $q, $ipbind) = @_; + $q ||= '255.255.255.255/32'; + + return $rs->search(undef,{ + # NOTE: bind param list order is significant + join => ['device_ips_by_address_or_name'], + bind => [$q, $ipbind, $q], + }); +} + +=head2 ports_with_mac( $mac ) + +Returns a correlated subquery for the set of C entries for each +device. The port MAC address matches the supplied C, using C. + +=cut + +sub ports_with_mac { + my ($rs, $mac) = @_; + $mac ||= '00:00:00:00:00:00'; + + return $rs->search(undef,{ + # NOTE: bind param list order is significant + join => ['ports_by_mac'], + bind => [$mac], + }); +} + =head2 with_times This is a modifier for any C (including the helpers below) which @@ -350,15 +387,17 @@ sub search_fuzzy { # basic IP check is a string match my $ip_clause = [ 'me.ip::text' => { '-ilike' => $q }, - 'device_ips.alias::text' => { '-ilike' => $q }, + 'device_ips_by_address_or_name.alias::text' => { '-ilike' => $q }, ]; + my $ipbind = '255.255.255.255/32'; # but also allow prefix search if (my $ip = NetAddr::IP::Lite->new($qc)) { $ip_clause = [ 'me.ip' => { '<<=' => $ip->cidr }, - 'device_ips.alias' => { '<<=' => $ip->cidr }, + 'device_ips_by_address_or_name.alias' => { '<<=' => $ip->cidr }, ]; + $ipbind = $ip->cidr; } # get IEEE MAC format @@ -369,7 +408,9 @@ sub search_fuzzy { or ($mac->as_ieee !~ m/$RE{net}{MAC}/))); $mac = ($mac ? $mac->as_ieee : $q); - return $rs->search( + return $rs->ports_with_mac($mac) + ->device_ips_with_address_or_name($q, $ipbind) + ->search( { -or => [ 'me.contact' => { '-ilike' => $q }, @@ -383,18 +424,17 @@ sub search_fuzzy { }, -or => [ 'me.mac::text' => { '-ilike' => $mac}, - 'ports.mac::text' => { '-ilike' => $mac}, + 'ports_by_mac.mac::text' => { '-ilike' => $mac}, ], -or => [ 'me.dns' => { '-ilike' => $q }, - 'device_ips.dns' => { '-ilike' => $q }, + 'device_ips_by_address_or_name.dns' => { '-ilike' => $q }, ], -or => $ip_clause, ], }, { order_by => [qw/ me.dns me.ip /], - join => [qw/device_ips ports/], distinct => 1, } );