svc_broadband merge
[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( qsearch qsearchs );
6 use FS::Record qw( qsearchs qsearch dbh );
7 use FS::svc_Common;
8 use FS::cust_svc;
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 =over 2
49
50 =item
51 The network consists of one or more 'Access Concentrators' (ACs), such as
52 DSLAMs or wireless access points.  (See L<FS::ac>.)
53
54 =item
55 Each AC provides connectivity to one or more contiguous blocks of IP addresses,
56 each described by a gateway address and a netmask.  (See L<FS::ac_block>.)
57
58 =item
59 Each connection has one or more static IP addresses within one of these blocks.
60
61 =item
62 The details of configuring routers and other devices are to be handled by a 
63 site-specific L<FS::part_export> subclass.
64
65 =back
66
67 FS::svc_broadband inherits from FS::svc_Common.  The following fields are
68 currently supported:
69
70 =over 4
71
72 =item svcnum - primary key
73
74 =item
75 actypenum - access concentrator type; see L<FS::ac_type>.  This is included here
76 so that a part_svc can specifically be a 'wireless' or 'DSL' service by
77 designating actypenum as a fixed field.  It does create a redundant functional
78 dependency between this table and ac_type, in that the matching ac_type could
79 be found by looking up the IP address in ac_block and then finding the block's
80 AC, but part_svc can't do that, and we don't feel like hacking it so that it
81 can.
82
83 =item
84 speed_up - maximum upload speed, in bits per second.  If set to zero, upload
85 speed will be unlimited.  Exports that do traffic shaping should handle this
86 correctly, and not blindly set the upload speed to zero and kill the customer's
87 connection.
88
89 =item
90 speed_down - maximum download speed, as above
91
92 =item
93 ip_addr - the customer's IP address.  If the customer needs more than one IP
94 address, set this to the address of the customer's router.  As a result, the
95 customer's router will have the same address for both it's internal and external
96 interfaces thus saving address space.  This has been found to work on most NAT
97 routers available.
98
99 =item
100 ip_netmask - the customer's netmask, as a single integer in the range 0-32.
101 (E.g. '24', not '255.255.255.0'.  We assume that address blocks are contiguous.)
102 This should be 32 unless the customer has multiple IP addresses.
103
104 =item
105 mac_addr - the MAC address of the customer's router or other device directly
106 connected to the network, if needed.  Some systems (e.g. DHCP, MAC address-based
107 access control) may need this.  If not, you may leave it blank.
108
109 =item
110 location - a human-readable description of the location of the connected site,
111 such as its address.  This should not be used for billing or contact purposes;
112 that information is stored in L<FS::cust_main>.
113
114 =back
115
116 =head1 METHODS
117
118 =over 4
119
120 =item new HASHREF
121
122 Creates a new svc_broadband.  To add the record to the database, see
123 L<"insert">.
124
125 Note that this stores the hash reference, not a distinct copy of the hash it
126 points to.  You can ask the object for a copy with the I<hash> method.
127
128 =cut
129
130 sub table { 'svc_broadband'; }
131
132 =item insert
133
134 Adds this record to the database.  If there is an error, returns the error,
135 otherwise returns false.
136
137 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be 
138 defined.  An FS::cust_svc record will be created and inserted.
139
140 =cut
141
142 # sub insert {}
143 # Standard FS::svc_Common::insert
144 # (any necessary Deep Magic is handled by exports)
145
146 =item delete
147
148 Delete this record from the database.
149
150 =cut
151
152 # Standard FS::svc_Common::delete
153
154 =item replace OLD_RECORD
155
156 Replaces the OLD_RECORD with this one in the database.  If there is an error,
157 returns the error, otherwise returns false.
158
159 =cut
160
161 # Standard FS::svc_Common::replace
162 # Notice a pattern here?
163
164 =item suspend
165
166 Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
167
168 =item unsuspend
169
170 Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
171
172 =item cancel
173
174 Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
175
176 =item check
177
178 Checks all fields to make sure this is a valid broadband service.  If there is
179 an error, returns the error, otherwise returns false.  Called by the insert
180 and replace methods.
181
182 =cut
183
184 sub check {
185   my $self = shift;
186   my $x = $self->setfixed;
187
188   return $x unless ref($x);
189
190   my $error =
191     $self->ut_numbern('svcnum')
192     || $self->ut_foreign_key('actypenum', 'ac_type', 'actypenum')
193     || $self->ut_number('speed_up')
194     || $self->ut_number('speed_down')
195     || $self->ut_ip('ip_addr')
196     || $self->ut_numbern('ip_netmask')
197     || $self->ut_textn('mac_addr')
198     || $self->ut_textn('location')
199   ;
200   return $error if $error;
201
202   if($self->speed_up < 0) { return 'speed_up must be positive'; }
203   if($self->speed_down < 0) { return 'speed_down must be positive'; }
204
205   # This should catch errors in the ip_addr and ip_netmask.  If it doesn't,
206   # they'll almost certainly not map into a valid block anyway.
207   my $self_addr = new NetAddr::IP ($self->ip_addr, $self->ip_netmask);
208   return 'Cannot parse address: ' . $self->ip_addr . '/' . $self->ip_netmask unless $self_addr;
209
210   my @block = grep { 
211     my $block_addr = new NetAddr::IP ($_->ip_gateway, $_->ip_netmask);
212     if ($block_addr->contains($self_addr)) { $_ };
213   } qsearch( 'ac_block', { acnum => $self->acnum });
214
215   if(scalar @block == 0) {
216     return 'Block not found for address '.$self->ip_addr.' in actype '.$self->actypenum;
217   } elsif(scalar @block > 1) {
218     return 'ERROR: Intersecting blocks found for address '.$self->ip_addr.' :'.
219         join ', ', map {$_->ip_addr . '/' . $_->ip_netmask} @block;
220   }
221   # OK, we've found a valid block.  We don't actually _do_ anything with it, though; we 
222   # just take comfort in the knowledge that it exists.
223
224   # A simple qsearchs won't work here.  Since we can assign blocks to customers,
225   # we have to make sure the new address doesn't fall within someone else's
226   # block.  Ugh.
227
228   my @conflicts = grep {
229     my $cust_addr = new NetAddr::IP($_->ip_addr, $_->ip_netmask);
230     if (($cust_addr->contains($self_addr)) and
231         ($_->svcnum ne $self->svcnum)) { $_; };
232   } qsearch('svc_broadband', {});
233
234   if (scalar @conflicts > 0) {
235     return 'Address in use by existing service';
236   }
237
238   # Are we trying to use a network, broadcast, or the AC's address?
239   foreach (qsearch('ac_block', { acnum => $self->acnum })) {
240     my $block_addr = new NetAddr::IP($_->ip_gateway, $_->ip_netmask);
241     if ($block_addr->network->addr eq $self_addr->addr) {
242       return 'Address is network address for block '. $block_addr->network;
243     }
244     if ($block_addr->broadcast->addr eq $self_addr->addr) {
245       return 'Address is broadcast address for block '. $block_addr->network;
246     }
247     if ($block_addr->addr eq $self_addr->addr) {
248       return 'Address belongs to the access concentrator: '. $block_addr->addr;
249     }
250   }
251
252   ''; #no error
253 }
254
255 =item ac_block
256
257 Returns the FS::ac_block record (i.e. the address block) for this broadband service.
258
259 =cut
260
261 sub ac_block {
262   my $self = shift;
263   my $self_addr = new NetAddr::IP ($self->ip_addr, $self->ip_netmask);
264
265   foreach my $block (qsearch( 'ac_block', {} )) {
266     my $block_addr = new NetAddr::IP ($block->ip_addr, $block->ip_netmask);
267     if($block_addr->contains($self_addr)) { return $block; }
268   }
269   return '';
270 }
271
272 =item ac_type
273
274 Returns the FS::ac_type record for this broadband service.
275
276 =cut
277
278 sub ac_type {
279   my $self = shift;
280   return qsearchs('ac_type', { actypenum => $self->actypenum });
281 }
282
283 =back
284
285 =head1 BUGS
286
287 =head1 SEE ALSO
288
289 L<FS::svc_Common>, L<FS::Record>, L<FS::ac_type>, L<FS::ac_block>,
290 L<FS::part_svc>, schema.html from the base documentation.
291
292 =cut
293
294 1;
295