diff options
author | khoff <khoff> | 2003-02-05 23:17:17 +0000 |
---|---|---|
committer | khoff <khoff> | 2003-02-05 23:17:17 +0000 |
commit | 0354f39ed0e74fd2eae1d9da13906625b4f56591 (patch) | |
tree | 730b2ac862f4c47c661d91a91ccb8167a4a0ee8f /FS | |
parent | c89aa83639038cc1946fec07a2dda252f64e5144 (diff) |
svc_broadband rewrite
Diffstat (limited to 'FS')
-rwxr-xr-x | FS/FS/addr_block.pm | 322 | ||||
-rwxr-xr-x | FS/FS/part_router_field.pm | 134 | ||||
-rwxr-xr-x | FS/FS/part_sb_field.pm | 267 | ||||
-rwxr-xr-x | FS/FS/part_svc_router.pm | 32 | ||||
-rwxr-xr-x | FS/FS/router.pm | 156 | ||||
-rwxr-xr-x | FS/FS/router_field.pm | 146 | ||||
-rwxr-xr-x | FS/FS/sb_field.pm | 148 | ||||
-rwxr-xr-x | FS/FS/svc_broadband.pm | 225 | ||||
-rwxr-xr-x | FS/bin/freeside-setup | 89 |
9 files changed, 1370 insertions, 149 deletions
diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm new file mode 100755 index 000000000..b671723aa --- /dev/null +++ b/FS/FS/addr_block.pm @@ -0,0 +1,322 @@ +package FS::addr_block; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs qsearch dbh ); +use FS::router; +use FS::svc_broadband; +use NetAddr::IP; + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::addr_block - Object methods for addr_block records + +=head1 SYNOPSIS + + use FS::addr_block; + + $record = new FS::addr_block \%hash; + $record = new FS::addr_block { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::addr_block record describes an address block assigned for broadband +access. FS::addr_block inherits from FS::Record. The following fields are +currently supported: + +=over 4 + +=item blocknum - primary key, used in FS::svc_broadband to associate +services to the block. + +=item routernum - the router (see FS::router) to which this +block is assigned. + +=item ip_gateway - the gateway address used by customers within this block. + +=item ip_netmask - the netmask of the block, expressed as an integer. + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Create a new record. To add the record to the database, see "insert". + +=cut + +sub table { 'addr_block'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=item delete + +Deletes this record from the database. If there is an error, returns the +error, otherwise returns false. + +sub delete { + my $self = shift; + return 'Block must be deallocated before deletion' + if $self->router; + + $self->SUPER::delete; +} + +=item replace OLD_RECORD + +Replaces OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid record. If there is an error, +returns the error, otherwise returns false. Called by the insert and replace +methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_number('routernum') + || $self->ut_ip('ip_gateway') + || $self->ut_number('ip_netmask') + ; + return $error if $error; + + + # A routernum of 0 indicates an unassigned block and is allowed + return "Unknown routernum" + if ($self->routernum and not $self->router); + + my $self_addr = $self->NetAddr; + return "Cannot parse address: ". $self->ip_gateway . '/' . $self->ip_netmask + unless $self_addr; + + if (not $self->blocknum) { + my @block = grep { + my $block_addr = $_->NetAddr; + if($block_addr->contains($self_addr) + or $self_addr->contains($block_addr)) { $_; }; + } qsearch( 'addr_block', {}); + foreach(@block) { + return "Block intersects existing block ".$_->ip_gateway."/".$_->ip_netmask; + } + } + + ''; +} + + +=item router + +Returns the FS::router object corresponding to this object. If the +block is unassigned, returns undef. + +=cut + +sub router { + my $self = shift; + return qsearchs('router', { routernum => $self->routernum }); +} + +=item svc_broadband + +Returns a list of FS::svc_broadband objects associated +with this object. + +=cut + +sub svc_broadband { + my $self = shift; + return qsearch('svc_broadband', { blocknum => $self->blocknum }); +} + +=item NetAddr + +Returns a NetAddr::IP object for this block's address and netmask. + +=cut + +sub NetAddr { + my $self = shift; + + return new NetAddr::IP ($self->ip_gateway, $self->ip_netmask); +} + +=item next_free_addr + +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. + +=cut + +sub next_free_addr { + my $self = shift; + + my @used = map { $_->NetAddr->addr } + ($self, + qsearch('svc_broadband', { blocknum => $self->blocknum }) ); + + my @free = $self->NetAddr->hostenum; + while (my $ip = shift @free) { + if (not grep {$_ eq $ip->addr;} @used) { return $ip; }; + } + + ''; + +} + +=item allocate + +Allocates this address block to a router. Takes an FS::router object +as an argument. + +At present it's not possible to reallocate a block to a different router +except by deallocating it first, which requires that none of its addresses +be assigned. This is probably as it should be. + +=cut + +sub allocate { + my ($self, $router) = @_; + + return 'Block is already allocated' + if($self->router); + + return 'Block must be allocated to a router' + unless(ref $router eq 'FS::router'); + + my @svc = $self->svc_broadband; + if (@svc) { + return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc; + } + + my $new = new FS::addr_block {$self->hash}; + $new->routernum($router->routernum); + return $new->replace($self); + +} + +=item deallocate + +Deallocates the block (i.e. sets the routernum to 0). If any addresses in the +block are assigned to services, it fails. + +=cut + +sub deallocate { + my $self = shift; + + my @svc = $self->svc_broadband; + if (@svc) { + return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc; + } + + my $new = new FS::addr_block {$self->hash}; + $new->routernum(0); + return $new->replace($self); +} + +=item split_block + +Splits this address block into two equal blocks, occupying the same space as +the original block. The first of the two will also have the same blocknum. +The gateway address of each block will be set to the first usable address, i.e. +(network address)+1. Since this method is designed for use on unallocated +blocks, this is probably the correct behavior. + +(At present, splitting allocated blocks is disallowed. Anyone who wants to +implement this is reminded that each split costs three addresses, and any +customers who were using these addresses will have to be moved; depending on +how full the block was before being split, they might have to be moved to a +different block. Anyone who I<still> wants to implement it is asked to tie it +to a configuration switch so that site admins can disallow it.) + +=cut + +sub split_block { + + # We should consider using Attribute::Handlers/Aspect/Hook::LexWrap/ + # something to atomicize functions, so that we can say + # + # sub split_block : atomic { + # + # instead of repeating all this AutoCommit verbage in every + # sub that does more than one database operation. + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $self = shift; + my $error; + + if ($self->router) { + return 'Block is already allocated'; + } + + #TODO: Smallest allowed block should be a config option. + if ($self->NetAddr->masklen() ge 30) { + return 'Cannot split blocks with a mask length >= 30'; + } + + my (@new, @ip); + $ip[0] = $self->NetAddr; + @ip = map {$_->first()} $ip[0]->split($self->ip_netmask + 1); + + foreach (0,1) { + $new[$_] = new FS::addr_block {$self->hash}; + $new[$_]->ip_gateway($ip[$_]->addr); + $new[$_]->ip_netmask($ip[$_]->masklen); + } + + $new[1]->blocknum(''); + + $error = $new[0]->replace($self); + if ($error) { + $dbh->rollback; + return $error; + } + + $error = $new[1]->insert; + if ($error) { + $dbh->rollback; + return $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + return ''; +} + +=item merge + +To be implemented. + +=back + +=head1 BUGS + +Minimum block size should be a config option. It's hardcoded at /30 right +now because that's the smallest block that makes any sense at all. + +1; + diff --git a/FS/FS/part_router_field.pm b/FS/FS/part_router_field.pm new file mode 100755 index 000000000..73ca50fb6 --- /dev/null +++ b/FS/FS/part_router_field.pm @@ -0,0 +1,134 @@ +package FS::part_router_field; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs ); +use FS::router_field; +use FS::router; + + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::part_router_field - Object methods for part_router_field records + +=head1 SYNOPSIS + + use FS::part_router_field; + + $record = new FS::part_router_field \%hash; + $record = new FS::part_router_field { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +A part_router_field represents an xfield definition for routers. For more +information on xfields, see L<FS::part_sb_field>. + +The following fields are supported: + +=over 4 + +=item routerfieldpart - primary key (assigned automatically) + +=item name - name of field + +=item length + +=item check_block + +=item list_source + +(See L<FS::part_sb_field> for details on these fields.) + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Create a new record. To add the record to the database, see "insert". + +=cut + +sub table { 'part_router_field'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=item delete + +Deletes this record from the database. If there is an error, returns the +error, otherwise returns false. + +=item replace OLD_RECORD + +Replaces OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid record. If there is an error, +returns the error, otherwise returns false. Called by the insert and replace +methods. + +=cut + +sub check { + my $self = shift; + my $error = ''; + + $self->name =~ /^([a-z0-9_\-\.]{1,15})$/i + or return "Invalid field name for part_router_field"; + + ''; #no error +} + +=item list_values + +Equivalent to "eval($part_router_field->list_source)". + +=cut + +sub list_values { + my $self = shift; + return () unless $self->list_source; + my @opts = eval($self->list_source); + if($@) { + warn $@; + return (); + } else { + return @opts; + } +} + +=back + +=head1 VERSION + +$Id: + +=head1 BUGS + +Needless duplication of much of FS::part_sb_field, with the result that most of +the warnings about it apply here also. + +=head1 SEE ALSO + +FS::svc_broadband, FS::router, FS::router_field, schema.html +from the base documentation. + +=cut + +1; + diff --git a/FS/FS/part_sb_field.pm b/FS/FS/part_sb_field.pm new file mode 100755 index 000000000..8dca946b5 --- /dev/null +++ b/FS/FS/part_sb_field.pm @@ -0,0 +1,267 @@ +package FS::part_sb_field; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs ); + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::part_sb_field - Object methods for part_sb_field records + +=head1 SYNOPSIS + + use FS::part_sb_field; + + $record = new FS::part_sb_field \%hash; + $record = new FS::part_sb_field { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::part_sb_field object represents an extended field (xfield) definition +for svc_broadband's sb_field mechanism (see L<FS::svc_broadband>). +FS::part_sb_field inherits from FS::Record. The following fields are +currently supported: + +=over 2 + +=item sbfieldpart - primary key (assigned automatically) + +=item name - name of the field + +=item svcpart - service type for which this field is available (see L<FS::part_svc>) + +=item length - length of the contents of the field (see note #1) + +=item check_block - validation routine (see note #2) + +=item list_source - enumeration routine (see note #3) + +=back + +=head1 BACKGROUND + +Broadband services, unlike dialup services, are provided over a wide +variety of physical media (DSL, wireless, cable modems, digital circuits) +and network architectures (Ethernet, PPP, ATM). For many of these access +mechanisms, adding a new customer requires knowledge of some properties +of the physical connection (circuit number, the type of CPE in use, etc.). +It is unreasonable to expect ISPs to alter Freeside's schema (and the +associated library and UI code) to make each of these parameters a field in +svc_broadband. + +Hence sb_field and part_sb_field. They allow the Freeside administrator to +define 'extended fields' ('xfields') associated with svc_broadband records. +These are I<not> processed in any way by Freeside itself; they exist solely for +use by exports (see L<FS::part_export>) and technical support staff. + +For a parallel mechanism (at the per-router level rather than per-service), +see L<FS::part_router_field>. + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Create a new record. To add the record to the database, see "insert". + +=cut + +sub table { 'part_sb_field'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=item delete + +Deletes this record from the database. If there is an error, returns the +error, otherwise returns false. + +=item replace OLD_RECORD + +Replaces OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid record. If there is an error, +returns the error, otherwise returns false. Called by the insert and replace +methods. + +=cut + +sub check { + my $self = shift; + my $error = ''; + + $error = $self->ut_numbern('svcpart'); + return $error if $error; + + unless (qsearchs('part_svc', { svcpart => $self->svcpart })) + { return "Unknown svcpart: " . $self->svcpart;} + + $self->name =~ /^([a-z0-9_\-\.]{1,15})$/i + or return "Invalid field name for part_sb_field"; + + #How to check input_block, display_block, and check_block? + + ''; #no error +} + +=item list_values + +If the I<list_source> field is set, this method eval()s it and +returns its output. If the field is empty, list_values returns +an empty list. + +Any arguments passed to this method will be received by the list_source +code, but this behavior is a fortuitous accident and may be removed in +the future. + +=cut + +sub list_values { + my $self = shift; + return () unless $self->list_source; + + my @opts = eval($self->list_source); + if($@) { + warn $@; + return (); + } else { + return @opts; + } +} + +=item part_svc + +Returns the FS::part_svc object associated with this field definition. + +=cut + +sub part_svc { + my $self = shift; + return qsearchs('part_svc', { svcpart => $self->svcpart }); +} + +=back + +=head1 VERSION + +$Id: + +=head1 NOTES + +=over + +=item 1. + +The I<length> field is not enforced. It provides a hint to UI +code about how to display the field on a form. If you want to enforce a +minimum or maximum length for a field, use a I<check_block>. + +=item 2. + +The check_block mechanism used here as well as in +FS::part_router_field allows the user to define validation rules. + +When FS::sb_field::check is called, the proposed value of the xfield is +assigned to $_. The check_block is then eval()'d and its return value +captured. If the return value is false (empty/zero/undef), $_ is then assigned +back into the field and stored in the database. + +Therefore a check_block can do three different things with the value: allow +it, allow it with a modification, or reject it. This is very flexible, but +somewhat dangerous. Some warnings: + +=over 2 + +=item * + +Assume that $_ has had I<no> error checking prior to the +check_block. That's what the check_block is for, after all. It could +contain I<anything>: evil shell commands in backquotes, 100kb JPEG images, +the Klez virus, whatever. + +=item * + +If your check_block modifies the input value, it should probably +produce a value that wouldn't be modified by going through the same +check_block again. (That is, it should map input values into its own +eigenspace.) The reason is that if someone calls $new->replace($old), +where $new and $old contain the same value for the field, they probably +want the field to keep its old value, not to get transformed by the +check_block again. So don't do silly things like '$_++' or +'tr/A-Za-z/a-zA-Z/'. + +=item * + +Don't alter the contents of the database. I<Reading> the database +is perfectly reasonable, but writing to it is a bad idea. Remember that +check() might get called more than once, as described above. + +=item * + +The check_block probably won't even get called if the user submits +an I<empty> sb_field. So at present, you can't set up a default value with +something like 's/^$/foo/'. Conversely, don't replace the submitted value +with an empty string. It probably will get stored, but might be deleted at +any time. + +=back + +=item 3. + +The list_source mechanism is a UI hint (like length) to generate +drop-down or list boxes. If list_source contains a value, the UI code can +eval() it and use the results as the options on the list. + +Note 'can'. This is not a substitute for check_block. The HTML interface +currently requires that the user pick one of the options on the list +because that's the way HTML drop-down boxes work, but in the future the UI +code might add an 'Other (please specify)' option and a text box so that +the user can enter something else. Or it might ignore list_source and just +generate a text box. Or the interface might be rewritten in MS Access, +where drop-down boxes have text boxes built in. Data validation is the job +of check(), not the front end. + +Note also that a list of literals evaluates to itself, so a list_source +like + +C<('Windows', 'MacOS', 'Linux')> + +or + +C<qw(Windows MacOS Linux)> + +means exactly what you'd think. + +=head1 BUGS + +The lack of any way to do default values. We might add this as another UI +hint (since, for the most part, it's the UI's job to figure out which fields +have had values entered into them). In fact, there are lots of things we +should add as UI hints. + +Oh, and the documentation is probably full of lies. + +=head1 SEE ALSO + +FS::svc_broadband, FS::sb_field, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/part_svc_router.pm b/FS/FS/part_svc_router.pm new file mode 100755 index 000000000..0b23ab580 --- /dev/null +++ b/FS/FS/part_svc_router.pm @@ -0,0 +1,32 @@ +package FS::part_svc_router; + +use strict; +use vars qw( @ISA ); +use FS::Record qw(qsearchs); +use FS::router; +use FS::part_svc; + +@ISA = qw(FS::Record); + +sub table { 'part_svc_router'; } + +sub check { + my $self = shift; + my $error = + $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart') + || $self->ut_foreign_key('routernum', 'router', 'routernum'); + return $error if $error; + ''; #no error +} + +sub router { + my $self = shift; + return qsearchs('router', { routernum => $self->routernum }); +} + +sub part_svc { + my $self = shift; + return qsearchs('part_svc', { svcpart => $self->svcpart }); +} + +1; diff --git a/FS/FS/router.pm b/FS/FS/router.pm new file mode 100755 index 000000000..3f9459a01 --- /dev/null +++ b/FS/FS/router.pm @@ -0,0 +1,156 @@ +package FS::router; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs qsearch ); +use FS::addr_block; + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::router - Object methods for router records + +=head1 SYNOPSIS + + use FS::router; + + $record = new FS::router \%hash; + $record = new FS::router { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::router record describes a broadband router, such as a DSLAM or a wireless + access point. FS::router inherits from FS::Record. The following +fields are currently supported: + +=over 4 + +=item routernum - primary key + +=item routername - descriptive name for the router + +=item svcnum - svcnum of the owning FS::svc_broadband, if appropriate + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Create a new record. To add the record to the database, see "insert". + +=cut + +sub table { 'router'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=item delete + +Deletes this record from the database. If there is an error, returns the +error, otherwise returns false. + +=item replace OLD_RECORD + +Replaces OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid record. If there is an error, +returns the error, otherwise returns false. Called by the insert and replace +methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('routernum') + || $self->ut_text('routername'); + return $error if $error; + + ''; +} + +=item addr_block + +Returns a list of FS::addr_block objects (address blocks) associated +with this object. + +=cut + +sub addr_block { + my $self = shift; + return qsearch('addr_block', { routernum => $self->routernum }); +} + +=item router_field + +Returns a list of FS::router_field objects assigned to this object. + +=cut + +sub router_field { + my $self = shift; + + return qsearch('router_field', { routernum => $self->routernum }); +} + +=item part_svc_router + +Returns a list of FS::part_svc_router objects associated with this +object. This is unlikely to be useful for any purpose other than retrieving +the associated FS::part_svc objects. See below. + +=cut + +sub part_svc_router { + my $self = shift; + return qsearch('part_svc_router', { routernum => $self->routernum }); +} + +=item part_svc + +Returns a list of FS::part_svc objects associated with this object. + +=cut + +sub part_svc { + my $self = shift; + return map { qsearchs('part_svc', { svcpart => $_->svcpart }) } + $self->part_svc_router; +} + +=back + +=head1 VERSION + +$Id: + +=head1 BUGS + +=head1 SEE ALSO + +FS::svc_broadband, FS::router, FS::addr_block, FS::router_field, FS::part_svc, +schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/router_field.pm b/FS/FS/router_field.pm new file mode 100755 index 000000000..eee21ab89 --- /dev/null +++ b/FS/FS/router_field.pm @@ -0,0 +1,146 @@ +package FS::router_field; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs ); +use FS::part_router_field; +use FS::router; + + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::router_field - Object methods for router_field records + +=head1 SYNOPSIS + + use FS::router_field; + + $record = new FS::router_field \%hash; + $record = new FS::router_field { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +FS::router_field contains values of router xfields. See FS::part_sb_field +for details on the xfield mechanism. + +=over 4 + +=item routerfieldpart - Type of router_field as defined by +FS::part_router_field + +=item routernum - The FS::router to which this value belongs. + +=item value - The contents of the field. + +=back + +=head1 METHODS + + +=over 4 + +=item new HASHREF + +Create a new record. To add the record to the database, see "insert". + +=cut + +sub table { 'router_field'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=item delete + +Deletes this record from the database. If there is an error, returns the +error, otherwise returns false. + +=item replace OLD_RECORD + +Replaces OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid record. If there is an error, +returns the error, otherwise returns false. Called by the insert and replace +methods. + +=cut + +sub check { + my $self = shift; + + return "routernum must be defined" unless $self->routernum; + return "routerfieldpart must be defined" unless $self->routerfieldpart; + + my $part_router_field = $self->part_router_field; + $_ = $self->value; + + my $check_block = $part_router_field->check_block; + if ($check_block) { + $@ = ''; + my $error = (eval($check_block) or $@); + return $error if $error; + $self->setfield('value' => $_); + } + + ''; #no error +} + +=item part_router_field + +Returns a reference to the FS:part_router_field that defines this +FS::router_field + +=cut + +sub part_router_field { + my $self = shift; + + return qsearchs('part_router_field', + { routerfieldpart => $self->routerfieldpart }); +} + +=item router + +Returns a reference to the FS::router to which this FS::router_field +belongs. + +=cut + +sub router { + my $self = shift; + + return qsearchs('router', { routernum => $self->routernum }); +} + +=back + +=head1 VERSION + +$Id: + +=head1 BUGS + +=head1 SEE ALSO + +FS::svc_broadband, FS::router, FS::router_block, FS::router_field, +schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/sb_field.pm b/FS/FS/sb_field.pm new file mode 100755 index 000000000..d4eb37844 --- /dev/null +++ b/FS/FS/sb_field.pm @@ -0,0 +1,148 @@ +package FS::sb_field; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearchs ); +use FS::part_sb_field; + +use UNIVERSAL qw( can ); + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::sb_field - Object methods for sb_field records + +=head1 SYNOPSIS + + use FS::sb_field; + + $record = new FS::sb_field \%hash; + $record = new FS::sb_field { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +See L<FS::part_sb_field> for details on this table's mission in life. +FS::sb_field contains the actual values of the xfields defined in +part_sb_field. + +The following fields are supported: + +=over 4 + +=item sbfieldpart - Type of sb_field as defined by FS::part_sb_field + +=item svcnum - The svc_broadband to which this value belongs. + +=item value - The contents of the field. + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Create a new record. To add the record to the database, see L<"insert">. + +=cut + +sub table { 'sb_field'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=item delete + +Deletes this record from the database. If there is an error, returns the +error, otherwise returns false. + +=item replace OLD_RECORD + +Replaces OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks the value against the check_block of the corresponding part_sb_field. +Returns whatever the check_block returned (unless the check_block dies, in +which case check returns the die message). Therefore, if the check_block +wants to allow the value to be stored, it must return false. See +L<FS::part_sb_field> for details. + +=cut + +sub check { + my $self = shift; + + return "svcnum must be defined" unless $self->svcnum; + return "sbfieldpart must be defined" unless $self->sbfieldpart; + + my $part_sb_field = $self->part_sb_field; + + $_ = $self->value; + + my $check_block = $self->part_sb_field->check_block; + if ($check_block) { + $@ = ''; + my $error = (eval($check_block) or $@); # treat fatal errors as errors + return $error if $error; + $self->setfield('value' => $_); + } + + ''; #no error +} + +=item part_sb_field + +Returns a reference to the FS::part_sb_field that defines this FS::sb_field. + +=cut + +sub part_sb_field { + my $self = shift; + + return qsearchs('part_sb_field', { sbfieldpart => $self->sbfieldpart }); +} + +=back + +=item svc_broadband + +Returns a reference to the FS::svc_broadband to which this value is attached. +Nobody's ever going to use this function, but here it is anyway. + +=cut + +sub svc_broadband { + my $self = shift; + + return qsearchs('svc_broadband', { svcnum => $self->svcnum }); +} + +=head1 VERSION + +$Id: + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::svc_broadband>, schema.html +from the base documentation. + +=cut + +1; + diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index ab92fb3d7..45f6c3601 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -2,10 +2,10 @@ package FS::svc_broadband; use strict; use vars qw(@ISA $conf); -#use FS::Record qw( qsearch qsearchs ); use FS::Record qw( qsearchs qsearch dbh ); use FS::svc_Common; use FS::cust_svc; +use FS::addr_block; use NetAddr::IP; @ISA = qw( FS::svc_Common ); @@ -45,25 +45,6 @@ An FS::svc_broadband object represents a 'broadband' Internet connection, such as a DSL, cable modem, or fixed wireless link. These services are assumed to have the following properties: -=over 2 - -=item -The network consists of one or more 'Access Concentrators' (ACs), such as -DSLAMs or wireless access points. (See L<FS::ac>.) - -=item -Each AC provides connectivity to one or more contiguous blocks of IP addresses, -each described by a gateway address and a netmask. (See L<FS::ac_block>.) - -=item -Each connection has one or more static IP addresses within one of these blocks. - -=item -The details of configuring routers and other devices are to be handled by a -site-specific L<FS::part_export> subclass. - -=back - FS::svc_broadband inherits from FS::svc_Common. The following fields are currently supported: @@ -71,14 +52,7 @@ currently supported: =item svcnum - primary key -=item -actypenum - access concentrator type; see L<FS::ac_type>. This is included here -so that a part_svc can specifically be a 'wireless' or 'DSL' service by -designating actypenum as a fixed field. It does create a redundant functional -dependency between this table and ac_type, in that the matching ac_type could -be found by looking up the IP address in ac_block and then finding the block's -AC, but part_svc can't do that, and we don't feel like hacking it so that it -can. +=item blocknum - see FS::addr_block =item speed_up - maximum upload speed, in bits per second. If set to zero, upload @@ -89,28 +63,12 @@ connection. =item speed_down - maximum download speed, as above -=item -ip_addr - the customer's IP address. If the customer needs more than one IP -address, set this to the address of the customer's router. As a result, the -customer's router will have the same address for both it's internal and external +=item ip_addr - the customer's IP address. If the customer needs more than one +IP address, set this to the address of the customer's router. As a result, the +customer's router will have the same address for both its internal and external interfaces thus saving address space. This has been found to work on most NAT routers available. -=item -ip_netmask - the customer's netmask, as a single integer in the range 0-32. -(E.g. '24', not '255.255.255.0'. We assume that address blocks are contiguous.) -This should be 32 unless the customer has multiple IP addresses. - -=item -mac_addr - the MAC address of the customer's router or other device directly -connected to the network, if needed. Some systems (e.g. DHCP, MAC address-based -access control) may need this. If not, you may leave it blank. - -=item -location - a human-readable description of the location of the connected site, -such as its address. This should not be used for billing or contact purposes; -that information is stored in L<FS::cust_main>. - =back =head1 METHODS @@ -120,7 +78,7 @@ that information is stored in L<FS::cust_main>. =item new HASHREF Creates a new svc_broadband. To add the record to the database, see -L<"insert">. +"insert". Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I<hash> method. @@ -134,14 +92,12 @@ sub table { 'svc_broadband'; } Adds this record to the database. If there is an error, returns the error, otherwise returns false. -The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be +The additional fields pkgnum and svcpart (see FS::cust_svc) should be defined. An FS::cust_svc record will be created and inserted. =cut -# sub insert {} # Standard FS::svc_Common::insert -# (any necessary Deep Magic is handled by exports) =item delete @@ -159,19 +115,62 @@ returns the error, otherwise returns false. =cut # Standard FS::svc_Common::replace -# Notice a pattern here? + +=item sb_field + +Returns a list of FS::sb_field objects assigned to this object. + +=cut + +sub sb_field { + my $self = shift; + + return qsearch( 'sb_field', { svcnum => $self->svcnum } ); +} + +=item sb_field_hashref + +Returns a hashref of the FS::sb_field key/value pairs for this object. + +Deprecated. Please don't use it. + +=cut + +# Kristian wrote this, but don't hold it against him. He was under a powerful +# distracting influence whom he evidently found much more interesting than +# svc_broadband.pm. I can't say I blame him. + +sub sb_field_hashref { + my $self = shift; + my $svcpart = shift; + + if ((not $svcpart) && ($self->cust_svc)) { + $svcpart = $self->cust_svc->svcpart; + } + + my $hashref = {}; + + map { + my $sb_field = qsearchs('sb_field', { sbfieldpart => $_->sbfieldpart, + svcnum => $self->svcnum }); + $hashref->{$_->getfield('name')} = $sb_field ? $sb_field->getfield('value') : ''; + } qsearch('part_sb_field', { svcpart => $svcpart }); + + return $hashref; + +} =item suspend -Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>). +Called by the suspend method of FS::cust_pkg (see FS::cust_pkg). =item unsuspend -Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>). +Called by the unsuspend method of FS::cust_pkg (see FS::cust_pkg). =item cancel -Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>). +Called by the cancel method of FS::cust_pkg (see FS::cust_pkg). =item check @@ -189,105 +188,99 @@ sub check { my $error = $self->ut_numbern('svcnum') - || $self->ut_foreign_key('actypenum', 'ac_type', 'actypenum') + || $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum') || $self->ut_number('speed_up') || $self->ut_number('speed_down') - || $self->ut_ip('ip_addr') - || $self->ut_numbern('ip_netmask') - || $self->ut_textn('mac_addr') - || $self->ut_textn('location') + || $self->ut_ipn('ip_addr') ; return $error if $error; if($self->speed_up < 0) { return 'speed_up must be positive'; } if($self->speed_down < 0) { return 'speed_down must be positive'; } - # This should catch errors in the ip_addr and ip_netmask. If it doesn't, - # they'll almost certainly not map into a valid block anyway. - my $self_addr = new NetAddr::IP ($self->ip_addr, $self->ip_netmask); - return 'Cannot parse address: ' . $self->ip_addr . '/' . $self->ip_netmask unless $self_addr; - - my @block = grep { - my $block_addr = new NetAddr::IP ($_->ip_gateway, $_->ip_netmask); - if ($block_addr->contains($self_addr)) { $_ }; - } qsearch( 'ac_block', { acnum => $self->acnum }); - - if(scalar @block == 0) { - return 'Block not found for address '.$self->ip_addr.' in actype '.$self->actypenum; - } elsif(scalar @block > 1) { - return 'ERROR: Intersecting blocks found for address '.$self->ip_addr.' :'. - join ', ', map {$_->ip_addr . '/' . $_->ip_netmask} @block; + if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') { + $self->ip_addr($self->addr_block->next_free_addr->addr); + if (not $self->ip_addr) { + return "No free addresses in addr_block (blocknum: ".$self->blocknum.")"; + } } - # OK, we've found a valid block. We don't actually _do_ anything with it, though; we - # just take comfort in the knowledge that it exists. - # A simple qsearchs won't work here. Since we can assign blocks to customers, - # we have to make sure the new address doesn't fall within someone else's - # block. Ugh. + # This should catch errors in the ip_addr. If it doesn't, + # they'll almost certainly not map into the block anyway. + my $self_addr = $self->NetAddr; #netmask is /32 + return ('Cannot parse address: ' . $self->ip_addr) unless $self_addr; - my @conflicts = grep { - my $cust_addr = new NetAddr::IP($_->ip_addr, $_->ip_netmask); - if (($cust_addr->contains($self_addr)) and - ($_->svcnum ne $self->svcnum)) { $_; }; - } qsearch('svc_broadband', {}); - - if (scalar @conflicts > 0) { - return 'Address in use by existing service'; + my $block_addr = $self->addr_block->NetAddr; + unless ($block_addr->contains($self_addr)) { + return 'blocknum '.$self->blocknum.' does not contain address '.$self->ip_addr; } - # Are we trying to use a network, broadcast, or the AC's address? - foreach (qsearch('ac_block', { acnum => $self->acnum })) { - my $block_addr = new NetAddr::IP($_->ip_gateway, $_->ip_netmask); - if ($block_addr->network->addr eq $self_addr->addr) { - return 'Address is network address for block '. $block_addr->network; - } - if ($block_addr->broadcast->addr eq $self_addr->addr) { - return 'Address is broadcast address for block '. $block_addr->network; - } - if ($block_addr->addr eq $self_addr->addr) { - return 'Address belongs to the access concentrator: '. $block_addr->addr; - } + my $router = $self->addr_block->router + or return 'Cannot assign address from unallocated block:'.$self->addr_block->blocknum; + if(grep { $_->routernum == $router->routernum} $self->allowed_routers) { + } # do nothing + else { + return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart; } + ''; #no error } -=item ac_block +=item NetAddr -Returns the FS::ac_block record (i.e. the address block) for this broadband service. +Returns a NetAddr::IP object containing the IP address of this service. The netmask +is /32. =cut -sub ac_block { +sub NetAddr { my $self = shift; - my $self_addr = new NetAddr::IP ($self->ip_addr, $self->ip_netmask); - - foreach my $block (qsearch( 'ac_block', {} )) { - my $block_addr = new NetAddr::IP ($block->ip_addr, $block->ip_netmask); - if($block_addr->contains($self_addr)) { return $block; } - } - return ''; + return new NetAddr::IP ($self->ip_addr); } -=item ac_type +=item addr_block -Returns the FS::ac_type record for this broadband service. +Returns the FS::addr_block record (i.e. the address block) for this broadband service. =cut -sub ac_type { +sub addr_block { my $self = shift; - return qsearchs('ac_type', { actypenum => $self->actypenum }); + + return qsearchs('addr_block', { blocknum => $self->blocknum }); } =back +=item allowed_routers + +Returns a list of allowed FS::router objects. + +=cut + +sub allowed_routers { + my $self = shift; + + return map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart }); +} + =head1 BUGS +I think there's one place in the code where we actually use sb_field_hashref. +That's a bug in itself. + +The real problem with it is that we're still grappling with the question of how +tightly xfields should be integrated with real fields. There are a few +different directions we could go with it--we I<could> override several +functions in Record so that xfields behave almost exactly like real fields (can +be set with setfield(), appear in fields() and hash(), used as criteria in +qsearch(), etc.). + =head1 SEE ALSO -L<FS::svc_Common>, L<FS::Record>, L<FS::ac_type>, L<FS::ac_block>, -L<FS::part_svc>, schema.html from the base documentation. +FS::svc_Common, FS::Record, FS::addr_block, FS::sb_field, +FS::part_svc, schema.html from the base documentation. =cut diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index 0ef3fc81b..19483765e 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -1012,76 +1012,99 @@ sub tables_hash_hack { 'index' => [], }, - 'ac_type' => { + 'router' => { 'columns' => [ - 'actypenum', 'serial', '', '', - 'actypename', 'varchar', '', $char_d, + 'routernum', 'serial', '', '', + 'routername', 'varchar', '', $char_d, + 'svcnum', 'int', '0', '', ], - 'primary_key' => 'actypenum', + 'primary_key' => 'routernum', 'unique' => [], 'index' => [], }, - 'ac' => { + 'part_svc_router' => { 'columns' => [ - 'acnum', 'serial', '', '', - 'actypenum', 'int', '', '', - 'acname', 'varchar', '', $char_d, - ], - 'primary_key' => 'acnum', + 'svcpart', 'int', '', '', + 'routernum', 'int', '', '', + ]; + 'primary_key' => '', 'unique' => [], - 'index' => [ [ 'actypenum' ] ], + 'index' => [], }, - 'part_ac_field' => { + 'part_router_field' => { 'columns' => [ - 'acfieldpart', 'serial', '', '', - 'actypenum', 'int', '', '', + 'routerfieldpart', 'serial', '', '', 'name', 'varchar', '', $char_d, - 'ut_type', 'varchar', '', $char_d, + 'length', 'int', '', '', + 'check_block', 'text', 'NULL', '', + 'list_source', 'text', 'NULL', '', ], - 'primary_key' => 'acfieldpart', + 'primary_key' => 'routerfieldpart', 'unique' => [], - 'index' => [ [ 'actypenum' ] ], + 'index' => [], }, - 'ac_field' => { + 'router_field' => { 'columns' => [ - 'acfieldpart', 'int', '', '', - 'acnum', 'int', '', '', - 'value', 'text', '', '', + 'routerfieldpart', 'int', '', '', + 'routernum', 'int', '', '', + 'value', 'varchar', '', 128, ], 'primary_key' => '', - 'unique' => [ [ 'acfieldpart', 'acnum' ] ], - 'index' => [ [ 'acnum' ] ], + 'unique' => [ [ 'routerfieldpart', 'routernum' ] ], + 'index' => [], }, - 'ac_block' => { + 'addr_block' => { 'columns' => [ - 'acnum', 'int', '', '', + 'blocknum', 'int', '', '', + 'routernum', 'int', '', '', 'ip_gateway', 'varchar', '', 15, 'ip_netmask', 'int', '', '', ], + 'primary_key' => 'blocknum', + 'unique' => [ [ 'blocknum', 'routernum' ] ], + 'index' => [], + }, + + 'part_sb_field' => { + 'columns' => [ + 'sbfieldpart', 'int', '', '', + 'svcpart', 'int', '', '', + 'name', 'varchar', '', $char_d, + 'length', 'int', '', '', + 'check_block', 'text', 'NULL', '', + 'list_source', 'text', 'NULL', '', + ], + 'primary_key' => 'sbfieldpart', + 'unique' => [ [ 'sbfieldpart', 'svcpart' ] ], + 'index' => [], + }, + + 'sb_field' => { + 'columns' => [ + 'sbfieldpart', 'int', '', '', + 'svcnum', 'int', '', '', + 'value', 'varchar', '', 128, + ], 'primary_key' => '', - 'unique' => [], - 'index' => [ [ 'acnum' ] ], + 'unique' => [ [ 'sbfieldpart', 'svcnum' ] ], + 'index' => [], }, 'svc_broadband' => { 'columns' => [ 'svcnum', 'int', '', '', - 'actypenum', 'int', '', '', + 'blocknum', 'int', '', '', 'speed_up', 'int', '', '', 'speed_down', 'int', '', '', - 'acnum', 'int', '', '', 'ip_addr', 'varchar', '', 15, - 'ip_netmask', 'int', '', '', - 'mac_addr', 'char', '', 17, - 'location', 'varchar', '', $char_d, ], 'primary_key' => 'svcnum', 'unique' => [], - 'index' => [ [ 'actypenum' ] ], + 'index' => [], }, ); |