1 package FS::part_export::trango;
3 =head1 FS::part_export::trango
5 This export sends SNMP SETs to a router using the Net::SNMP package. It requires the following custom fields to be defined on a router. If any of the required custom fields are not present, then the export will exit quietly.
7 =head1 Required custom fields
11 =item trango_address - IP address (or hostname) of the Trango AP.
13 =item trango_comm - R/W SNMP community of the Trango AP.
15 =item trango_ap_type - Trango AP Model. Currently 'access5830' is the only supported option.
19 =head1 Optional custom fields
23 =item trango_baseid - Base ID of the Trango AP. See L</"Generating SU IDs">.
25 =item trango_apid - AP ID of the Trango AP. See L</"Generating SU IDs">.
29 =head1 Generating SU IDs
31 This export will/must generate a unique SU ID for each service exported to a Trango AP. It can be done such that SU IDs are globally unique, unique per Base ID, or unique per Base ID/AP ID pair. This is accomplished by setting neither trango_baseid and trango_apid, only trango_baseid, or both trango_baseid and trango_apid, respectively. An SU ID will be generated if the FS::svc_broadband virtual field specified by suid_field export option is unset, otherwise the existing value will be used.
35 This export has been tested with the Trango Access5830 AP.
42 use vars qw(@ISA %info $me $DEBUG $trango_mib $counter_dir);
44 use FS::UID qw(dbh datasrc);
45 use FS::Record qw(qsearch qsearchs);
46 use FS::part_export::snmp;
49 use File::CounterFile;
50 use Data::Dumper qw(Dumper);
52 @ISA = qw(FS::part_export::snmp);
54 tie my %options, 'Tie::IxHash', (
56 'label' => 'Trango SU ID field',
57 'default' => 'trango_suid',
58 'notes' => 'Name of the FS::svc_broadband virtual field that will contain the SU ID.',
61 'label' => 'Trango MAC address field',
63 'notes' => 'Name of the FS::svc_broadband virtual field that will contain the SU\'s MAC address.',
68 'svc' => 'svc_broadband',
69 'desc' => 'Sends SNMP SETs to a Trango AP.',
70 'options' => \%options,
72 'notes' => 'Requires Net::SNMP. See the documentation for FS::part_export::trango for required virtual fields and usage information.',
75 $me= '[' . __PACKAGE__ . ']';
80 'snmpversion' => 'snmpv1',
84 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
86 'value' => \&_trango_access5830_sudbDeleteOrAddId,
89 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2',
90 'type' => 'HEX_STRING',
91 'value' => \&_trango_access5830_sudbAddMac,
94 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7',
100 { # sudbDeleteOrAddID
101 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
103 'value' => \&_trango_access5830_sudbDeleteOrAddId,
106 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8',
112 { # sudbDeleteOrAddID
113 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
115 'value' => \&_trango_access5830_sudbDeleteOrAddId,
118 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8',
122 { # sudbDeleteOrAddID
123 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
125 'value' => \&_trango_access5830_sudbDeleteOrAddId,
128 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2',
129 'type' => 'HEX_STRING',
130 'value' => \&_trango_access5830_sudbAddMac,
133 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7',
139 { # sudbDeleteOrAddID
140 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
142 'value' => \&_trango_access5830_sudbDeleteOrAddId,
145 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8',
151 { # sudbDeleteOrAddID
152 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1',
154 'value' => \&_trango_access5830_sudbDeleteOrAddId,
157 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2',
158 'type' => 'HEX_STRING',
159 'value' => \&_trango_access5830_sudbAddMac,
162 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7',
172 sub _field_prefix { 'trango'; }
174 sub _req_router_fields {
176 $_[0]->_field_prefix . '_' . $_
177 } (qw(address comm ap_type suid_field));
182 return('FS::part_export::snmp::snmp_cmd');
188 my ($self, $action, $router) = (shift, shift, shift);
189 my ($svc_broadband) = shift;
190 my $old = shift if $action eq 'replace';
191 my $field_prefix = $self->_field_prefix;
194 my $ap_type = $router->getfield($field_prefix . '_ap_type');
196 unless (exists $trango_mib->{$ap_type}) {
197 return "Unsupported Trango AP type '$ap_type'";
200 $error = $self->_check_suid(
201 $action, $router, $svc_broadband, ($old) ? $old : ()
203 return $error if $error;
205 $error = $self->_check_mac(
206 $action, $router, $svc_broadband, ($old) ? $old : ()
208 return $error if $error;
210 my $ap_mib = $trango_mib->{$ap_type};
213 '-hostname' => $router->getfield($field_prefix.'_address'),
214 '-version' => $ap_mib->{'snmpversion'},
215 '-community' => $router->getfield($field_prefix.'_comm'),
218 my @varbindlist = ();
220 foreach my $oid (@{$ap_mib->{'varbinds'}->{$action}}) {
221 warn "[debug]$me Processing OID '" . $oid->{'oid'} . "'" if $DEBUG;
223 if (ref($oid->{'value'}) eq 'CODE') {
225 $value = &{$oid->{'value'}}(
226 $self, $action, $router, $svc_broadband,
227 (($old) ? $old : ()),
230 return "While processing OID '" . $oid->{'oid'} . "':" . $@
233 $value = $oid->{'value'};
236 warn "[debug]$me Value for OID '" . $oid->{'oid'} . "': " if $DEBUG;
238 if (defined $value) { # Skip OIDs with undefined values.
239 push @varbindlist, ($oid->{'oid'}, $oid->{'type'}, $value);
244 push @$args, ('-varbindlist', @varbindlist);
252 my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift);
253 my $old = shift if $action eq 'replace';
256 my $suid_field = $self->option('suid_field');
257 unless (grep {$_ eq $suid_field} $svc_broadband->fields) {
258 return "Missing Trango SU ID field. "
259 . "See the trango export options for more info.";
262 my $suid = $svc_broadband->getfield($suid_field);
263 if ($action eq 'replace') {
264 my $old_suid = $old->getfield($suid_field);
266 if ($old_suid ne '' and $old_suid ne $suid) {
267 return 'Cannot change Trango SU ID';
271 if (not $suid =~ /^\d+$/ and $action ne 'delete') {
272 my $new_suid = eval { $self->_get_next_suid($router); };
273 return "Error while getting next Trango SU ID: $@" if ($@);
275 warn "[debug]$me Got new SU ID: $new_suid" if $DEBUG;
276 $svc_broadband->set($suid_field, $new_suid);
278 #FIXME: Probably a bad hack.
279 # We need to update the SU ID field in the database.
281 my $oldAutoCommit = $FS::UID::AutoCommit;
282 local $FS::svc_Common::noexport_hack = 1;
283 local $FS::UID::AutoCommit = 0;
286 my $svcnum = $svc_broadband->svcnum;
288 my $old_svc = qsearchs('svc_broadband', { svcnum => $svcnum });
290 return "Unable to retrieve svc_broadband with svcnum '$svcnum";
293 my $svcpart = $svc_broadband->svcpart
294 ? $svc_broadband->svcpart
295 : $svc_broadband->cust_svc->svcpart;
297 my $new_svc = new FS::svc_broadband {
299 $suid_field => $new_suid,
303 $error = $new_svc->check;
305 $dbh->rollback if $oldAutoCommit;
306 return "Error while updating the Trango SU ID: $error" if $error;
309 warn "[debug]$me Updating svc_broadband with SU ID '$new_suid'...\n" .
310 &Dumper($new_svc) if $DEBUG;
312 $error = eval { $new_svc->replace($old_svc); };
316 $dbh->rollback if $oldAutoCommit;
317 return "Error while updating the Trango SU ID: $error" if $error;
320 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
330 my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift);
331 my $old = shift if $action eq 'replace';
333 my $mac_field = $self->option('mac_field');
334 unless (grep {$_ eq $mac_field} $svc_broadband->fields) {
335 return "Missing Trango MAC address field. "
336 . "See the trango export options for more info.";
339 my $mac_addr = $svc_broadband->getfield($mac_field);
340 unless (length(join('', $mac_addr =~ /[0-9a-fA-F]/g)) == 12) {
341 return "Invalid Trango MAC address: $mac_addr";
350 my ($self, $router) = (shift, shift);
352 my $counter_dir = '/usr/local/etc/freeside/export.'. datasrc . '/trango';
353 my $baseid = $router->getfield('trango_baseid');
354 my $apid = $router->getfield('trango_apid');
356 my $counter_file_suffix = '';
358 $counter_file_suffix .= "_B$baseid";
360 $counter_file_suffix .= "_A$apid";
364 my $counter_file = $counter_dir . '/SUID' . $counter_file_suffix;
366 warn "[debug]$me Using SUID counter file '$counter_file'";
369 mkdir $counter_dir, 0700 unless -d $counter_dir;
371 my $cf = new File::CounterFile($counter_file, 0);
375 die "Error generating next Trango SU ID: $@" if (not $suid or $@);
383 # Trango-specific subroutines for generating varbind values.
385 # All subs should die on error, and return undef to decline. OIDs that
386 # decline will not be added to varbinds.
388 sub _trango_access5830_sudbDeleteOrAddId {
390 my ($self, $action, $router) = (shift, shift, shift);
391 my ($svc_broadband) = shift;
392 my $old = shift if $action eq 'replace';
394 my $suid = $svc_broadband->getfield($self->option('suid_field'));
397 unless ($suid =~ /^\d+$/) {
398 if ($action eq 'delete') {
399 # Silently ignore. If we don't have a valid SU ID now, we probably
403 die "Invalid Trango SU ID '$suid'";
411 sub _trango_access5830_sudbAddMac {
413 my ($self, $action, $router) = (shift, shift, shift);
414 my ($svc_broadband) = shift;
415 my $old = shift if $action eq 'replace';
417 my $mac_addr = $svc_broadband->getfield($self->option('mac_field'));
418 $mac_addr = join('', $mac_addr =~ /[0-9a-fA-F]/g);
421 die "Invalid Trango MAC address '$mac_addr'" unless (length($mac_addr)==12);