Update httemplate/elements/selectlayers.html
[freeside.git] / FS / FS / part_export / broadband_snmp.pm
1 package FS::part_export::broadband_snmp;
2
3 use strict;
4 use vars qw(%info $DEBUG);
5 use base 'FS::part_export';
6 use Net::SNMP qw(:asn1 :snmp);
7 use Tie::IxHash;
8
9 $DEBUG = 0;
10
11 my $me = '['.__PACKAGE__.']';
12
13 tie my %snmp_version, 'Tie::IxHash',
14   v1  => 'snmpv1',
15   v2c => 'snmpv2c',
16   # 3 => 'v3' not implemented
17 ;
18
19 tie my %snmp_type, 'Tie::IxHash',
20   i => INTEGER,
21   u => UNSIGNED32,
22   s => OCTET_STRING,
23   n => NULL,
24   o => OBJECT_IDENTIFIER,
25   t => TIMETICKS,
26   a => IPADDRESS,
27   # others not implemented yet
28 ;
29
30 tie my %options, 'Tie::IxHash',
31   'version' => { label=>'SNMP version', 
32     type => 'select',
33     options => [ keys %snmp_version ],
34    },
35   'community' => { label=>'Community', default=>'public' },
36   (
37     map { $_.'_command', 
38           { label => ucfirst($_) . ' commands',
39             type  => 'textarea',
40             default => '',
41           }
42     } qw( insert delete replace suspend unsuspend )
43   ),
44   'ip_addr_change_to_new' => { 
45     label=>'Send IP address changes to new address',
46     type=>'checkbox'
47   },
48   'timeout' => { label=>'Timeout (seconds)' },
49 ;
50
51 %info = (
52   'svc'     => 'svc_broadband',
53   'desc'    => 'Send SNMP requests to the service IP address',
54   'options' => \%options,
55   'no_machine' => 1,
56   'weight'  => 10,
57   'notes'   => <<'END'
58 Send one or more SNMP SET requests to the IP address registered to the service.
59 Enter one command per line.  Each command is a target OID, data type flag,
60 and value, separated by spaces.
61 The data type flag is one of the following:
62 <font size="-1"><ul>
63 <li><i>i</i> = INTEGER</li>
64 <li><i>u</i> = UNSIGNED32</li>
65 <li><i>s</i> = OCTET-STRING (as ASCII)</li>
66 <li><i>a</i> = IPADDRESS</li>
67 <li><i>n</i> = NULL</li></ul>
68 The value may interpolate fields from svc_broadband by prefixing the field 
69 name with <b>$</b>, or <b>$new_</b> and <b>$old_</b> for replace operations.
70 The value may contain whitespace; quotes are not necessary.<br>
71 <br>
72 For example, to set the SNMPv2-MIB "sysName.0" object to the string 
73 "svc_broadband" followed by the service number, use the following 
74 command:<br>
75 <pre>1.3.6.1.2.1.1.5.0 s svc_broadband$svcnum</pre><br>
76 END
77 );
78
79 sub export_insert {
80   my $self = shift;
81   $self->export_command('insert', @_);
82 }
83
84 sub export_delete {
85   my $self = shift;
86   $self->export_command('delete', @_);
87 }
88
89 sub export_replace {
90   my $self = shift;
91   $self->export_command('replace', @_);
92 }
93
94 sub export_suspend {
95   my $self = shift;
96   $self->export_command('suspend', @_);
97 }
98
99 sub export_unsuspend {
100   my $self = shift;
101   $self->export_command('unsuspend', @_);
102 }
103
104 sub export_command {
105   my $self = shift;
106   my ($action, $svc_new, $svc_old) = @_;
107
108   my $command_text = $self->option($action.'_command');
109   return if !length($command_text);
110
111   warn "$me parsing ${action}_command:\n" if $DEBUG;
112   my @commands;
113   foreach (split /\n/, $command_text) {
114     my ($oid, $type, $value) = split /\s/, $_, 3;
115     $oid =~ /^(\d+\.)*\d+$/ or die "invalid OID '$oid'\n";
116     my $typenum = $snmp_type{$type} or die "unknown data type '$type'\n";
117     $value = '' if !defined($value); # allow sending an empty string
118     $value = $self->substitute($value, $svc_new, $svc_old);
119     warn "$me     $oid $type $value\n" if $DEBUG;
120     push @commands, $oid, $typenum, $value;
121   }
122
123   my $ip_addr = $svc_new->ip_addr;
124   # ip address change: send to old address unless told otherwise
125   if ( defined $svc_old and ! $self->option('ip_addr_change_to_new') ) {
126     $ip_addr = $svc_old->ip_addr;
127   }
128   warn "$me opening session to $ip_addr\n" if $DEBUG;
129
130   my %opt = (
131     -hostname => $ip_addr,
132     -community => $self->option('community'),
133     -timeout => $self->option('timeout') || 20,
134   );
135   my $version = $self->option('version');
136   $opt{-version} = $snmp_version{$version} or die 'invalid version';
137   $opt{-varbindlist} = \@commands; # just for now
138
139   $self->snmp_queue( $svc_new->svcnum, %opt );
140 }
141
142 sub snmp_queue {
143   my $self = shift;
144   my $svcnum = shift;
145   my $queue = new FS::queue {
146     'svcnum'  => $svcnum,
147     'job'     => 'FS::part_export::broadband_snmp::snmp_request',
148   };
149   $queue->insert(@_);
150 }
151
152 sub snmp_request {
153   my %opt = @_;
154   my $varbindlist = delete $opt{-varbindlist};
155   my ($session, $error) = Net::SNMP->session(%opt);
156   die "Couldn't create SNMP session: $error" if !$session;
157
158   warn "$me sending SET request\n" if $DEBUG;
159   my $result = $session->set_request( -varbindlist => $varbindlist );
160   $error = $session->error();
161   $session->close();
162
163   if (!defined $result) {
164     die "SNMP request failed: $error\n";
165   }
166 }
167
168 sub substitute {
169   # double-quote-ish interpolation of service fields
170   # accepts old_ and new_ for replace actions, like shellcommands
171   my $self = shift;
172   my ($value, $svc_new, $svc_old) = @_;
173   foreach my $field ( $svc_new->fields ) {
174     my $new_val = $svc_new->$field;
175     $value =~ s/\$(new_)?$field/$new_val/g;
176     if ( $svc_old ) { # replace only
177       my $old_val = $svc_old->$field;
178       $value =~ s/\$old_$field/$old_val/g;
179     }
180   }
181   $value;
182 }
183
184 1;