package FS::part_export::broadband_snmp; use strict; use vars qw(%info $DEBUG); use base 'FS::part_export'; use SNMP; use Tie::IxHash; $DEBUG = 0; my $me = '['.__PACKAGE__.']'; tie my %snmp_version, 'Tie::IxHash', v1 => '1', v2c => '2c', # v3 unimplemented ; #tie my %snmp_type, 'Tie::IxHash', # i => INTEGER, # u => UNSIGNED32, # s => OCTET_STRING, # n => NULL, # o => OBJECT_IDENTIFIER, # t => TIMETICKS, # a => IPADDRESS, # # others not implemented yet #; tie my %options, 'Tie::IxHash', 'version' => { label=>'SNMP version', type => 'select', options => [ keys %snmp_version ], }, 'community' => { label=>'Community', default=>'public' }, 'action' => { multiple=>1 }, 'oid' => { multiple=>1 }, 'value' => { multiple=>1 }, 'datatype'=> { multiple=>1 }, 'ip_addr_change_to_new' => { label=>'Send IP address changes to new address', type=>'checkbox' }, 'timeout' => { label=>'Timeout (seconds)' }, ; %info = ( 'svc' => 'svc_broadband', 'desc' => 'Send SNMP requests to the service IP address', 'config_element' => '/edit/elements/part_export/broadband_snmp.html', 'options' => \%options, 'no_machine' => 1, 'weight' => 10, 'notes' => <<'END' Send one or more SNMP SET requests to the IP address registered to the service. The value may interpolate fields from svc_broadband, cust_location, or cust_main by prefixing the field name with $. For replace operations, svc_broadband fields may be prefixed with $new_ and $old_ (e.g. "$old_mac_addr"). END ); sub _export_insert { my $self = shift; $self->export_command('insert', @_); } sub _export_delete { my $self = shift; $self->export_command('delete', @_); } sub _export_replace { my $self = shift; $self->export_command('replace', @_); } sub _export_suspend { my $self = shift; $self->export_command('suspend', @_); } sub _export_unsuspend { my $self = shift; $self->export_command('unsuspend', @_); } sub export_command { my $self = shift; my ($action, $svc_new, $svc_old) = @_; my @a = split("\n", $self->option('action')); my @o = split("\n", $self->option('oid')); my @v = split("\n", $self->option('value')); my @commands; warn "$me parsing $action commands:\n" if $DEBUG; while (@a) { my $oid = shift @o; my $value = shift @v; next unless shift(@a) eq $action; # ignore commands for other actions $value = $self->substitute($value, $svc_new, $svc_old); warn "$me $oid :=$value\n" if $DEBUG; push @commands, $oid, $value; } my $ip_addr = $svc_new->ip_addr; # ip address change: send to old address unless told otherwise if ( defined $svc_old and ! $self->option('ip_addr_change_to_new') ) { $ip_addr = $svc_old->ip_addr; } warn "$me opening session to $ip_addr\n" if $DEBUG; my %opt = ( DestHost => $ip_addr, Community => $self->option('community'), Timeout => ($self->option('timeout') || 20) * 1000, ); my $version = $self->option('version'); $opt{Version} = $snmp_version{$version} or die 'invalid version'; $opt{VarList} = \@commands; # for now $self->snmp_queue( $svc_new->svcnum, %opt ); } sub snmp_queue { my $self = shift; my $svcnum = shift; my $queue = new FS::queue { 'svcnum' => $svcnum, 'job' => 'FS::part_export::broadband_snmp::snmp_request', }; $queue->insert(@_); } sub snmp_request { my %opt = @_; my $flatvarlist = delete $opt{VarList}; my $session = SNMP::Session->new(%opt); warn "$me sending SET request\n" if $DEBUG; my @varlist; while (@$flatvarlist) { my @this = splice(@$flatvarlist, 0, 2); push @varlist, [ $this[0], 0, $this[1], undef ]; # XXX new option to choose the IID (array index) of the object? } $session->set(\@varlist); my $error = $session->{ErrorStr}; if ( $session->{ErrorNum} ) { die "SNMP request failed: $error\n"; } } sub substitute { # double-quote-ish interpolation of service fields # accepts old_ and new_ for replace actions, like shellcommands my $self = shift; my ($value, $svc_new, $svc_old) = @_; my $location = $svc_new->cust_svc->cust_pkg->cust_location; my $cust_main = $location->cust_main; foreach my $field ( $svc_new->fields ) { my $new_val = $svc_new->$field; $value =~ s/\$(new_)?$field/$new_val/g; if ( $svc_old ) { # replace only my $old_val = $svc_old->$field; $value =~ s/\$old_$field/$old_val/g; } } # we don't yet have export_relocate hooks in here, so there's no old/new # cust_location. do cust_location before cust_main, since cust_main has # a bunch of empty fields with the same names. foreach my $field ( $location->fields ) { my $curr_val = $location->get($field); $value =~ s/\$$field/$curr_val/g; } foreach my $field ( $cust_main->fields ) { my $curr_val = $cust_main->get($field); $value =~ s/\$$field/$curr_val/g; } $value; } sub _upgrade_exporttype { eval 'use FS::Record qw(qsearch qsearchs)'; # change from old style with numeric oid, data type flag, and value # on consecutive lines foreach my $export (qsearch('part_export', { exporttype => 'broadband_snmp' } )) { # for the new options my %new_options = ( 'action' => [], 'oid' => [], 'value' => [], ); foreach my $action (qw(insert replace delete suspend unsuspend)) { my $old_option = qsearchs('part_export_option', { exportnum => $export->exportnum, optionname => $action.'_command' } ); next if !$old_option; my $text = $old_option->optionvalue; my @commands = split("\n", $text); foreach (@commands) { my ($oid, $type, $value) = split /\s/, $_, 3; push @{$new_options{action}}, $action; push @{$new_options{oid}}, $oid; push @{$new_options{value}}, $value; } my $error = $old_option->delete; warn "error migrating ${action}_command option: $error\n" if $error; } foreach (keys(%new_options)) { my $new_option = FS::part_export_option->new({ exportnum => $export->exportnum, optionname => $_, optionvalue => join("\n", @{ $new_options{$_} }) }); my $error = $new_option->insert; warn "error inserting '$_' option: $error\n" if $error; } } #foreach $export ''; } 1;