Merge branch 'patch-1' of https://github.com/gjones2/Freeside
[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 SNMP;
7 use Tie::IxHash;
8
9 $DEBUG = 0;
10
11 my $me = '['.__PACKAGE__.']';
12
13 tie my %snmp_version, 'Tie::IxHash',
14   v1  => '1',
15   v2c => '2c',
16   # v3 unimplemented
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   'action' => { multiple=>1 },
38   'oid'    => { multiple=>1 },
39   'value'  => { multiple=>1 },
40
41   'ip_addr_change_to_new' => { 
42     label=>'Send IP address changes to new address',
43     type=>'checkbox'
44   },
45   'timeout' => { label=>'Timeout (seconds)' },
46 ;
47
48 %info = (
49   'svc'     => 'svc_broadband',
50   'desc'    => 'Send SNMP requests to the service IP address',
51   'config_element' => '/edit/elements/part_export/broadband_snmp.html',
52   'options' => \%options,
53   'no_machine' => 1,
54   'weight'  => 10,
55   'notes'   => <<'END'
56 Send one or more SNMP SET requests to the IP address registered to the service.
57 The value may interpolate fields from svc_broadband by prefixing the field 
58 name with <b>$</b>, or <b>$new_</b> and <b>$old_</b> for replace operations.
59 END
60 );
61
62 sub export_insert {
63   my $self = shift;
64   $self->export_command('insert', @_);
65 }
66
67 sub export_delete {
68   my $self = shift;
69   $self->export_command('delete', @_);
70 }
71
72 sub export_replace {
73   my $self = shift;
74   $self->export_command('replace', @_);
75 }
76
77 sub export_suspend {
78   my $self = shift;
79   $self->export_command('suspend', @_);
80 }
81
82 sub export_unsuspend {
83   my $self = shift;
84   $self->export_command('unsuspend', @_);
85 }
86
87 sub export_command {
88   my $self = shift;
89   my ($action, $svc_new, $svc_old) = @_;
90
91   my @a = split("\n", $self->option('action'));
92   my @o = split("\n", $self->option('oid'));
93   my @v = split("\n", $self->option('value'));
94   my @commands;
95   warn "$me parsing $action commands:\n" if $DEBUG;
96   while (@a) {
97     my $oid = shift @o;
98     my $value = shift @v;
99     next unless shift(@a) eq $action; # ignore commands for other actions
100     $value = $self->substitute($value, $svc_new, $svc_old);
101     warn "$me     $oid :=$value\n" if $DEBUG;
102     push @commands, $oid, $value;
103   }
104
105   my $ip_addr = $svc_new->ip_addr;
106   # ip address change: send to old address unless told otherwise
107   if ( defined $svc_old and ! $self->option('ip_addr_change_to_new') ) {
108     $ip_addr = $svc_old->ip_addr;
109   }
110   warn "$me opening session to $ip_addr\n" if $DEBUG;
111
112   my %opt = (
113     DestHost  => $ip_addr,
114     Community => $self->option('community'),
115     Timeout   => ($self->option('timeout') || 20) * 1000,
116   );
117   my $version = $self->option('version');
118   $opt{Version} = $snmp_version{$version} or die 'invalid version';
119   $opt{VarList} = \@commands; # for now
120
121   $self->snmp_queue( $svc_new->svcnum, %opt );
122 }
123
124 sub snmp_queue {
125   my $self = shift;
126   my $svcnum = shift;
127   my $queue = new FS::queue {
128     'svcnum'  => $svcnum,
129     'job'     => 'FS::part_export::broadband_snmp::snmp_request',
130   };
131   $queue->insert(@_);
132 }
133
134 sub snmp_request {
135   my %opt = @_;
136   my $flatvarlist = delete $opt{VarList};
137   my $session = SNMP::Session->new(%opt);
138
139   warn "$me sending SET request\n" if $DEBUG;
140
141   my @varlist;
142   while (@$flatvarlist) {
143     my @this = splice(@$flatvarlist, 0, 2);
144     push @varlist, [ $this[0], 0, $this[1], undef ];
145     # XXX new option to choose the IID (array index) of the object?
146   }
147
148   $session->set(\@varlist);
149   my $error = $session->{ErrorStr};
150
151   if ( $session->{ErrorNum} ) {
152     die "SNMP request failed: $error\n";
153   }
154 }
155
156 sub substitute {
157   # double-quote-ish interpolation of service fields
158   # accepts old_ and new_ for replace actions, like shellcommands
159   my $self = shift;
160   my ($value, $svc_new, $svc_old) = @_;
161   foreach my $field ( $svc_new->fields ) {
162     my $new_val = $svc_new->$field;
163     $value =~ s/\$(new_)?$field/$new_val/g;
164     if ( $svc_old ) { # replace only
165       my $old_val = $svc_old->$field;
166       $value =~ s/\$old_$field/$old_val/g;
167     }
168   }
169   $value;
170 }
171
172 sub _upgrade_exporttype {
173   eval 'use FS::Record qw(qsearch qsearchs)';
174   # change from old style with numeric oid, data type flag, and value
175   # on consecutive lines
176   foreach my $export (qsearch('part_export',
177                       { exporttype => 'broadband_snmp' } ))
178   {
179     # for the new options
180     my %new_options = (
181       'action' => [],
182       'oid'    => [],
183       'value'  => [],
184     );
185     foreach my $action (qw(insert replace delete suspend unsuspend)) {
186       my $old_option = qsearchs('part_export_option',
187                       { exportnum   => $export->exportnum,
188                         optionname  => $action.'_command' } );
189       next if !$old_option;
190       my $text = $old_option->optionvalue;
191       my @commands = split("\n", $text);
192       foreach (@commands) {
193         my ($oid, $type, $value) = split /\s/, $_, 3;
194         push @{$new_options{action}}, $action;
195         push @{$new_options{oid}},    $oid;
196         push @{$new_options{value}},   $value;
197       }
198       my $error = $old_option->delete;
199       warn "error migrating ${action}_command option: $error\n" if $error;
200     }
201     foreach (keys(%new_options)) {
202       my $new_option = FS::part_export_option->new({
203           exportnum   => $export->exportnum,
204           optionname  => $_,
205           optionvalue => join("\n", @{ $new_options{$_} })
206       });
207       my $error = $new_option->insert;
208       warn "error inserting '$_' option: $error\n" if $error;
209     }
210   } #foreach $export
211   '';
212 }
213
214 1;