svc_circuit, #23879, #25933, #30830
[freeside.git] / FS / FS / addr_block.pm
index 5815f1a..7687334 100755 (executable)
@@ -4,10 +4,13 @@ use strict;
 use vars qw( @ISA );
 use FS::Record qw( qsearchs qsearch dbh );
 use FS::router;
 use vars qw( @ISA );
 use FS::Record qw( qsearchs qsearch dbh );
 use FS::router;
+use FS::addr_range;
 use FS::svc_broadband;
 use FS::Conf;
 use FS::svc_broadband;
 use FS::Conf;
+use FS::IP_Mixin;
 use NetAddr::IP;
 use Carp qw( carp );
 use NetAddr::IP;
 use Carp qw( carp );
+use List::Util qw( first );
 
 @ISA = qw( FS::Record );
 
 
 @ISA = qw( FS::Record );
 
@@ -48,6 +51,8 @@ block is assigned.
 
 =item ip_netmask - the netmask of the block, expressed as an integer.
 
 
 =item ip_netmask - the netmask of the block, expressed as an integer.
 
+=item manual_flag - prohibit automatic ip assignment from this block when true. 
+
 =item agentnum - optional agent number (see L<FS::agent>)
 
 =back
 =item agentnum - optional agent number (see L<FS::agent>)
 
 =back
@@ -74,14 +79,35 @@ otherwise returns false.
 Deletes this record from the database.  If there is an error, returns the
 error, otherwise returns false.
 
 Deletes this record from the database.  If there is an error, returns the
 error, otherwise returns false.
 
+=cut
+
 sub delete {
   my $self = shift;
 sub delete {
   my $self = shift;
-  return 'Block must be deallocated before deletion'
-    if $self->router;
-
-  $self->SUPER::delete;
+  return 'Block must be deallocated and have no services before deletion'
+    if $self->router || $self->svc_broadband;
+
+    local $SIG{HUP} = 'IGNORE';
+    local $SIG{INT} = 'IGNORE';
+    local $SIG{QUIT} = 'IGNORE';
+    local $SIG{TERM} = 'IGNORE';
+    local $SIG{TSTP} = 'IGNORE';
+    local $SIG{PIPE} = 'IGNORE';
+
+    my $oldAutoCommit = $FS::UID::AutoCommit;
+    local $FS::UID::AutoCommit = 0;
+    my $dbh = dbh;
+    
+    my $error = $self->SUPER::delete;
+    if ( $error ) {
+       $dbh->rollback if $oldAutoCommit;
+       return $error;
+    }
+  
+    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+    '';
 }
 
 }
 
+
 =item replace OLD_RECORD
 
 Replaces OLD_RECORD with this one in the database.  If there is an error,
 =item replace OLD_RECORD
 
 Replaces OLD_RECORD with this one in the database.  If there is an error,
@@ -124,7 +150,8 @@ sub check {
     $self->ut_number('routernum')
     || $self->ut_ip('ip_gateway')
     || $self->ut_number('ip_netmask')
     $self->ut_number('routernum')
     || $self->ut_ip('ip_gateway')
     || $self->ut_number('ip_netmask')
-    || $self->ut_agentnum_acl('agentnum', 'Engineering global configuration')
+    || $self->ut_enum('manual_flag', [ '', 'Y' ])
+    || $self->ut_agentnum_acl('agentnum', 'Broadband global configuration')
   ;
   return $error if $error;
 
   ;
   return $error if $error;
 
@@ -202,29 +229,48 @@ sub cidr {
 
 Returns a NetAddr::IP object corresponding to the first unassigned address 
 in the block (other than the network, broadcast, or gateway address).  If 
 
 Returns a NetAddr::IP object corresponding to the first unassigned address 
 in the block (other than the network, broadcast, or gateway address).  If 
-there are no free addresses, returns false.
+there are no free addresses, returns nothing.  There are never free addresses
+when manual_flag is true.
+
+There is no longer a method to return all free addresses in a block.
 
 =cut
 
 sub next_free_addr {
   my $self = shift;
 
 =cut
 
 sub next_free_addr {
   my $self = shift;
+  my $selfaddr = $self->NetAddr;
+
+  return () if $self->manual_flag;
 
   my $conf = new FS::Conf;
   my @excludeaddr = $conf->config('exclude_ip_addr');
 
   my $conf = new FS::Conf;
   my @excludeaddr = $conf->config('exclude_ip_addr');
-  
-my @used =
-( (map { $_->NetAddr->addr }
-    ($self,
-     qsearch('svc_broadband', { blocknum => $self->blocknum }))
-  ), @excludeaddr
-);
-
-  my @free = $self->NetAddr->hostenum;
-  while (my $ip = shift @free) {
-    if (not grep {$_ eq $ip->addr;} @used) { return $ip; };
-  }
 
 
-  '';
+  my %used = map { $_ => 1 }
+  (
+    @excludeaddr,
+    $selfaddr->addr,
+    $selfaddr->network->addr,
+    $selfaddr->broadcast->addr,
+    FS::IP_Mixin->used_addresses($self)
+  );
+
+  # just do a linear search of the block
+  my $freeaddr = $selfaddr->network + 1;
+  while ( $freeaddr < $selfaddr->broadcast ) {
+    # also make sure it's not blocked from assignment by an address range
+    if ( !$used{$freeaddr->addr } ) {
+      my ($range) = grep { !$_->allow_use }
+                  FS::addr_range->any_contains($freeaddr->addr);
+      if ( !$range ) {
+        # then we've found a free address
+        return $freeaddr;
+      }
+      # otherwise, skip to the end of the range
+      $freeaddr = NetAddr::IP->new($range->end, $self->ip_netmask);
+    }
+    $freeaddr++;
+  }
+  return;
 
 }
 
 
 }
 
@@ -366,8 +412,6 @@ sub label {
   ($router ? $router->routername : '(unallocated)'). ':'. $self->NetAddr;
 }
 
   ($router ? $router->routername : '(unallocated)'). ':'. $self->NetAddr;
 }
 
-=back
-
 =head1 BUGS
 
 Minimum block size should be a config option.  It's hardcoded at /30 right
 =head1 BUGS
 
 Minimum block size should be a config option.  It's hardcoded at /30 right