When next_free_addr returned undef, we were trying to call addr on an undefined objec...
[freeside.git] / FS / FS / svc_broadband.pm
1 package FS::svc_broadband;
2
3 use strict;
4 use vars qw(@ISA $conf);
5 use FS::Record qw( qsearchs qsearch dbh );
6 use FS::svc_Common;
7 use FS::cust_svc;
8 use FS::addr_block;
9 use NetAddr::IP;
10
11 @ISA = qw( FS::svc_Common );
12
13 $FS::UID::callback{'FS::svc_broadband'} = sub { 
14   $conf = new FS::Conf;
15 };
16
17 =head1 NAME
18
19 FS::svc_broadband - Object methods for svc_broadband records
20
21 =head1 SYNOPSIS
22
23   use FS::svc_broadband;
24
25   $record = new FS::svc_broadband \%hash;
26   $record = new FS::svc_broadband { 'column' => 'value' };
27
28   $error = $record->insert;
29
30   $error = $new_record->replace($old_record);
31
32   $error = $record->delete;
33
34   $error = $record->check;
35
36   $error = $record->suspend;
37
38   $error = $record->unsuspend;
39
40   $error = $record->cancel;
41
42 =head1 DESCRIPTION
43
44 An FS::svc_broadband object represents a 'broadband' Internet connection, such
45 as a DSL, cable modem, or fixed wireless link.  These services are assumed to
46 have the following properties:
47
48 FS::svc_broadband inherits from FS::svc_Common.  The following fields are
49 currently supported:
50
51 =over 4
52
53 =item svcnum - primary key
54
55 =item blocknum - see FS::addr_block
56
57 =item
58 speed_up - maximum upload speed, in bits per second.  If set to zero, upload
59 speed will be unlimited.  Exports that do traffic shaping should handle this
60 correctly, and not blindly set the upload speed to zero and kill the customer's
61 connection.
62
63 =item
64 speed_down - maximum download speed, as above
65
66 =item ip_addr - the customer's IP address.  If the customer needs more than one
67 IP address, set this to the address of the customer's router.  As a result, the
68 customer's router will have the same address for both its internal and external
69 interfaces thus saving address space.  This has been found to work on most NAT
70 routers available.
71
72 =back
73
74 =head1 METHODS
75
76 =over 4
77
78 =item new HASHREF
79
80 Creates a new svc_broadband.  To add the record to the database, see
81 "insert".
82
83 Note that this stores the hash reference, not a distinct copy of the hash it
84 points to.  You can ask the object for a copy with the I<hash> method.
85
86 =cut
87
88 sub table { 'svc_broadband'; }
89
90 =item insert
91
92 Adds this record to the database.  If there is an error, returns the error,
93 otherwise returns false.
94
95 The additional fields pkgnum and svcpart (see FS::cust_svc) should be 
96 defined.  An FS::cust_svc record will be created and inserted.
97
98 =cut
99
100 # Standard FS::svc_Common::insert
101
102 =item delete
103
104 Delete this record from the database.
105
106 =cut
107
108 # Standard FS::svc_Common::delete
109
110 =item replace OLD_RECORD
111
112 Replaces the OLD_RECORD with this one in the database.  If there is an error,
113 returns the error, otherwise returns false.
114
115 =cut
116
117 # Standard FS::svc_Common::replace
118
119 =item suspend
120
121 Called by the suspend method of FS::cust_pkg (see FS::cust_pkg).
122
123 =item unsuspend
124
125 Called by the unsuspend method of FS::cust_pkg (see FS::cust_pkg).
126
127 =item cancel
128
129 Called by the cancel method of FS::cust_pkg (see FS::cust_pkg).
130
131 =item check
132
133 Checks all fields to make sure this is a valid broadband service.  If there is
134 an error, returns the error, otherwise returns false.  Called by the insert
135 and replace methods.
136
137 =cut
138
139 sub check {
140   my $self = shift;
141   my $x = $self->setfixed;
142
143   return $x unless ref($x);
144
145   my $error =
146     $self->ut_numbern('svcnum')
147     || $self->ut_foreign_key('blocknum', 'addr_block', 'blocknum')
148     || $self->ut_number('speed_up')
149     || $self->ut_number('speed_down')
150     || $self->ut_ipn('ip_addr')
151   ;
152   return $error if $error;
153
154   if($self->speed_up < 0) { return 'speed_up must be positive'; }
155   if($self->speed_down < 0) { return 'speed_down must be positive'; }
156
157   if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') {
158     my $next_addr = $self->addr_block->next_free_addr;
159     if ($next_addr) {
160       $self->ip_addr($next_addr->addr);
161     } else {
162       return "No free addresses in addr_block (blocknum: ".$self->blocknum.")";
163     }
164   }
165
166   # This should catch errors in the ip_addr.  If it doesn't,
167   # they'll almost certainly not map into the block anyway.
168   my $self_addr = $self->NetAddr; #netmask is /32
169   return ('Cannot parse address: ' . $self->ip_addr) unless $self_addr;
170
171   my $block_addr = $self->addr_block->NetAddr;
172   unless ($block_addr->contains($self_addr)) {
173     return 'blocknum '.$self->blocknum.' does not contain address '.$self->ip_addr;
174   }
175
176   my $router = $self->addr_block->router 
177     or return 'Cannot assign address from unallocated block:'.$self->addr_block->blocknum;
178   if(grep { $_->routernum == $router->routernum} $self->allowed_routers) {
179   } # do nothing
180   else {
181     return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart;
182   }
183
184   $self->SUPER::check;
185 }
186
187 =item NetAddr
188
189 Returns a NetAddr::IP object containing the IP address of this service.  The netmask 
190 is /32.
191
192 =cut
193
194 sub NetAddr {
195   my $self = shift;
196   return new NetAddr::IP ($self->ip_addr);
197 }
198
199 =item addr_block
200
201 Returns the FS::addr_block record (i.e. the address block) for this broadband service.
202
203 =cut
204
205 sub addr_block {
206   my $self = shift;
207
208   return qsearchs('addr_block', { blocknum => $self->blocknum });
209 }
210
211 =back
212
213 =item allowed_routers
214
215 Returns a list of allowed FS::router objects.
216
217 =cut
218
219 sub allowed_routers {
220   my $self = shift;
221
222   return map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart });
223 }
224
225 =head1 BUGS
226
227 The business with sb_field has been 'fixed', in a manner of speaking.
228
229 =head1 SEE ALSO
230
231 FS::svc_Common, FS::Record, FS::addr_block,
232 FS::part_svc, schema.html from the base documentation.
233
234 =cut
235
236 1;
237