X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fpart_export%2Ftrango.pm;fp=FS%2FFS%2Fpart_export%2Ftrango.pm;h=e7f1126ddb40093735f16884b969e388a4ce9b5e;hb=d11482aaa34f11b5007741e2099af46750805c11;hp=0000000000000000000000000000000000000000;hpb=c4f427df48d37522ac5822bd9eca5e9151044e19;p=freeside.git diff --git a/FS/FS/part_export/trango.pm b/FS/FS/part_export/trango.pm new file mode 100644 index 000000000..e7f1126dd --- /dev/null +++ b/FS/FS/part_export/trango.pm @@ -0,0 +1,434 @@ +package FS::part_export::trango; + +=head1 FS::part_export::trango + +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. + +=head1 Required custom fields + +=over 4 + +=item trango_address - IP address (or hostname) of the Trango AP. + +=item trango_comm - R/W SNMP community of the Trango AP. + +=item trango_ap_type - Trango AP Model. Currently 'access5830' is the only supported option. + +=back + +=head1 Optional custom fields + +=over 4 + +=item trango_baseid - Base ID of the Trango AP. See L. + +=item trango_apid - AP ID of the Trango AP. See L. + +=back + +=head1 Generating SU IDs + +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. + +=head1 Device Support + +This export has been tested with the Trango Access5830 AP. + + +=cut + + +use strict; +use vars qw(@ISA %info $me $DEBUG $trango_mib $counter_dir); + +use FS::UID qw(dbh datasrc); +use FS::Record qw(qsearch qsearchs); +use FS::part_export::snmp; + +use Tie::IxHash; +use File::CounterFile; +use Data::Dumper qw(Dumper); + +@ISA = qw(FS::part_export::snmp); + +tie my %options, 'Tie::IxHash', ( + 'suid_field' => { + 'label' => 'Trango SU ID field', + 'default' => 'trango_suid', + 'notes' => 'Name of the FS::svc_broadband virtual field that will contain the SU ID.', + }, + 'mac_field' => { + 'label' => 'Trango MAC address field', + 'default' => '', + 'notes' => 'Name of the FS::svc_broadband virtual field that will contain the SU\'s MAC address.', + }, +); + +%info = ( + 'svc' => 'svc_broadband', + 'desc' => 'Sends SNMP SETs to a Trango AP.', + 'options' => \%options, + 'notes' => 'Requires Net::SNMP. See the documentation for FS::part_export::trango for required virtual fields and usage information.', +); + +$me= '[' . __PACKAGE__ . ']'; +$DEBUG = 1; + +$trango_mib = { + 'access5830' => { + 'snmpversion' => 'snmpv1', + 'varbinds' => { + 'insert' => [ + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbAddMac + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2', + 'type' => 'HEX_STRING', + 'value' => \&_trango_access5830_sudbAddMac, + }, + { # sudbAddSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7', + 'type' => 'INTEGER', + 'value' => 1, + }, + ], + 'delete' => [ + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbDeleteSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8', + 'type' => 'INTEGER', + 'value' => 1, + }, + ], + 'replace' => [ + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbDeleteSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8', + 'type' => 'INTEGER', + 'value' => 1, + }, + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbAddMac + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2', + 'type' => 'HEX_STRING', + 'value' => \&_trango_access5830_sudbAddMac, + }, + { # sudbAddSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7', + 'type' => 'INTEGER', + 'value' => 1, + }, + ], + 'suspend' => [ + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbDeleteSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.8', + 'type' => 'INTEGER', + 'value' => 1, + }, + ], + 'unsuspend' => [ + { # sudbDeleteOrAddID + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.1', + 'type' => 'INTEGER', + 'value' => \&_trango_access5830_sudbDeleteOrAddId, + }, + { # sudbAddMac + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.2', + 'type' => 'HEX_STRING', + 'value' => \&_trango_access5830_sudbAddMac, + }, + { # sudbAddSU + 'oid' => '1.3.6.1.4.1.5454.1.20.3.5.7', + 'type' => 'INTEGER', + 'value' => 1, + }, + ], + }, + }, +}; + + +sub _field_prefix { 'trango'; } + +sub _req_router_fields { + map { + $_[0]->_field_prefix . '_' . $_ + } (qw(address comm ap_type suid_field)); +} + +sub _get_cmd_sub { + + return('FS::part_export::snmp::snmp_cmd'); + +} + +sub _prepare_args { + + my ($self, $action, $router) = (shift, shift, shift); + my ($svc_broadband) = shift; + my $old = shift if $action eq 'replace'; + my $field_prefix = $self->_field_prefix; + my $error; + + my $ap_type = $router->getfield($field_prefix . '_ap_type'); + + unless (exists $trango_mib->{$ap_type}) { + return "Unsupported Trango AP type '$ap_type'"; + } + + $error = $self->_check_suid( + $action, $router, $svc_broadband, ($old) ? $old : () + ); + return $error if $error; + + $error = $self->_check_mac( + $action, $router, $svc_broadband, ($old) ? $old : () + ); + return $error if $error; + + my $ap_mib = $trango_mib->{$ap_type}; + + my $args = [ + '-hostname' => $router->getfield($field_prefix.'_address'), + '-version' => $ap_mib->{'snmpversion'}, + '-community' => $router->getfield($field_prefix.'_comm'), + ]; + + my @varbindlist = (); + + foreach my $oid (@{$ap_mib->{'varbinds'}->{$action}}) { + warn "[debug]$me Processing OID '" . $oid->{'oid'} . "'" if $DEBUG; + my $value; + if (ref($oid->{'value'}) eq 'CODE') { + eval { + $value = &{$oid->{'value'}}( + $self, $action, $router, $svc_broadband, + (($old) ? $old : ()), + ); + }; + return "While processing OID '" . $oid->{'oid'} . "':" . $@ + if $@; + } else { + $value = $oid->{'value'}; + } + + warn "[debug]$me Value for OID '" . $oid->{'oid'} . "': " if $DEBUG; + + if (defined $value) { # Skip OIDs with undefined values. + push @varbindlist, ($oid->{'oid'}, $oid->{'type'}, $value); + } + } + + + push @$args, ('-varbindlist', @varbindlist); + + return('', $args); + +} + +sub _check_suid { + + my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift); + my $old = shift if $action eq 'replace'; + my $error; + + my $suid_field = $self->option('suid_field'); + unless (grep {$_ eq $suid_field} $svc_broadband->fields) { + return "Missing Trango SU ID field. " + . "See the trango export options for more info."; + } + + my $suid = $svc_broadband->getfield($suid_field); + if ($action eq 'replace') { + my $old_suid = $old->getfield($suid_field); + + if ($old_suid ne '' and $old_suid ne $suid) { + return 'Cannot change Trango SU ID'; + } + } + + if (not $suid =~ /^\d+$/ and $action ne 'delete') { + my $new_suid = eval { $self->_get_next_suid($router); }; + return "Error while getting next Trango SU ID: $@" if ($@); + + warn "[debug]$me Got new SU ID: $new_suid" if $DEBUG; + $svc_broadband->set($suid_field, $new_suid); + + #FIXME: Probably a bad hack. + # We need to update the SU ID field in the database. + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::svc_Common::noexport_hack = 1; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $svcnum = $svc_broadband->svcnum; + + my $old_svc = qsearchs('svc_broadband', { svcnum => $svcnum }); + unless ($old_svc) { + return "Unable to retrieve svc_broadband with svcnum '$svcnum"; + } + + my $svcpart = $svc_broadband->svcpart + ? $svc_broadband->svcpart + : $svc_broadband->cust_svc->svcpart; + + my $new_svc = new FS::svc_broadband { + $old_svc->hash, + $suid_field => $new_suid, + svcpart => $svcpart, + }; + + $error = $new_svc->check; + if ($error) { + $dbh->rollback if $oldAutoCommit; + return "Error while updating the Trango SU ID: $error" if $error; + } + + warn "[debug]$me Updating svc_broadband with SU ID '$new_suid'...\n" . + &Dumper($new_svc) if $DEBUG; + + $error = eval { $new_svc->replace($old_svc); }; + + if ($@ or $error) { + $error ||= $@; + $dbh->rollback if $oldAutoCommit; + return "Error while updating the Trango SU ID: $error" if $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + } + + return ''; + +} + +sub _check_mac { + + my ($self, $action, $router, $svc_broadband) = (shift, shift, shift, shift); + my $old = shift if $action eq 'replace'; + + my $mac_field = $self->option('mac_field'); + unless (grep {$_ eq $mac_field} $svc_broadband->fields) { + return "Missing Trango MAC address field. " + . "See the trango export options for more info."; + } + + my $mac_addr = $svc_broadband->getfield($mac_field); + unless (length(join('', $mac_addr =~ /[0-9a-fA-F]/g)) == 12) { + return "Invalid Trango MAC address: $mac_addr"; + } + + return(''); + +} + +sub _get_next_suid { + + my ($self, $router) = (shift, shift); + + my $counter_dir = '/usr/local/etc/freeside/export.'. datasrc . '/trango'; + my $baseid = $router->getfield('trango_baseid'); + my $apid = $router->getfield('trango_apid'); + + my $counter_file_suffix = ''; + if ($baseid ne '') { + $counter_file_suffix .= "_B$baseid"; + if ($apid ne '') { + $counter_file_suffix .= "_A$apid"; + } + } + + my $counter_file = $counter_dir . '/SUID' . $counter_file_suffix; + + warn "[debug]$me Using SUID counter file '$counter_file'"; + + my $suid = eval { + mkdir $counter_dir, 0700 unless -d $counter_dir; + + my $cf = new File::CounterFile($counter_file, 0); + $cf->inc; + }; + + die "Error generating next Trango SU ID: $@" if (not $suid or $@); + + return($suid); + +} + + + +# Trango-specific subroutines for generating varbind values. +# +# All subs should die on error, and return undef to decline. OIDs that +# decline will not be added to varbinds. + +sub _trango_access5830_sudbDeleteOrAddId { + + my ($self, $action, $router) = (shift, shift, shift); + my ($svc_broadband) = shift; + my $old = shift if $action eq 'replace'; + + my $suid = $svc_broadband->getfield($self->option('suid_field')); + + # Sanity check. + unless ($suid =~ /^\d+$/) { + if ($action eq 'delete') { + # Silently ignore. If we don't have a valid SU ID now, we probably + # never did. + return undef; + } else { + die "Invalid Trango SU ID '$suid'"; + } + } + + return ($suid); + +} + +sub _trango_access5830_sudbAddMac { + + my ($self, $action, $router) = (shift, shift, shift); + my ($svc_broadband) = shift; + my $old = shift if $action eq 'replace'; + + my $mac_addr = $svc_broadband->getfield($self->option('mac_field')); + $mac_addr = join('', $mac_addr =~ /[0-9a-fA-F]/g); + + # Sanity check. + die "Invalid Trango MAC address '$mac_addr'" unless (length($mac_addr)==12); + + return($mac_addr); + +} + + +=head1 BUGS + +Plenty, I'm sure. + +=cut + + +1;