add port option, RT#7051
[freeside.git] / FS / FS / part_export / prizm.pm
1 package FS::part_export::prizm;
2
3 use vars qw(@ISA %info %options $DEBUG $me);
4 use Tie::IxHash;
5 use FS::Record qw(fields dbh);
6 use FS::part_export;
7
8 @ISA = qw(FS::part_export);
9 $DEBUG = 0;
10 $me = '[' . __PACKAGE__ . ']';
11
12 tie %options, 'Tie::IxHash',
13   'url'      => { label => 'Northbound url', default=>'https://localhost:8443/prizm/nbi' },
14   'user'     => { label => 'Northbound username', default=>'nbi' },
15   'password' => { label => 'Password', default => '' },
16   'ems'      => { label => 'Full EMS', type => 'checkbox' },
17   'always_bam' => { label => 'Always activate/suspend authentication', type => 'checkbox' },
18   'element_name_length' => { label => 'Size of siteName (best left blank)' },
19 ;
20
21 my $notes = <<'EOT';
22 Real-time export of <b>svc_broadband</b>, <b>cust_pkg</b>, and <b>cust_main</b>
23 record data to Motorola
24 <a href="http://motorola.canopywireless.com/products/prizm/">Canopy Prizm
25 software</a> via the Northbound interface.<br><br>
26
27 Freeside will attempt to create an element in an existing network with the
28 values provided in svc_broadband.  Of particular interest are
29 <ul>
30   <li> mac address - used to identify the element
31   <li> vlan profile - an exact match for a vlan profiles defined in prizm
32   <li> ip address - defines the management ip address of the prizm element
33   <li> latitude - GPS latitude
34   <li> longitude - GPS longitude
35   <li> altitude - GPS altitude
36 </ul>
37
38 In addition freeside attempts to set the service plan name in prizm to the
39 name of the package in which the service resides.
40
41 The service is associated with a customer in prizm as well, and freeside
42 will create the customer should none already exist with import id matching
43 the freeside customer number.  The following fields are set.
44
45 <ul>
46   <li> importId - the freeside customer number
47   <li> customerType - freeside
48   <li> customerName - the name associated with the freeside shipping address
49   <li> address1 - the shipping address
50   <li> address2
51   <li> city
52   <li> state
53   <li> zipCode
54   <li> country
55   <li> workPhone - the daytime phone number
56   <li> homePhone - the night phone number
57   <li> freesideId - the freeside customer number
58 </ul>
59
60   Additionally set on the element are
61 <ul>
62   <li> Site Name - The shipping name followed by the service broadband description field
63   <li> Site Location - the shipping address
64   <li> Site Contact - the daytime and night phone numbers
65 </ul>
66
67 Freeside provisions, suspends, and unsuspends elements BAM only unless the
68 'Full EMS' checkbox is checked.<br><br>
69
70 When freeside provisions an element the siteName is copied internally by
71 prizm in such a manner that it is possible for the value to exceed the size
72 of the column used in the prizm database.  Therefore freeside truncates
73 by default this value to 50 characters.  It is thought that this
74 column is the account_name column of the element_user_account table.  It
75 may be possible to lift this limit by modifying the prizm database and
76 setting a new appropriate value on this export.  This is untested and
77 possibly harmful.
78
79 EOT
80
81 %info = (
82   'svc'      => 'svc_broadband',
83   'desc'     => 'Real-time export to Northbound Interface',
84   'options'  => \%options,
85   'nodomain' => 'Y',
86   'notes'    => $notes,
87 );
88
89 sub prizm_command {
90   my ($self,$namespace,$method) = (shift,shift,shift);
91
92   eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
93   die $@ if $@;
94
95   my $prizm = new Net::Prizm (
96     namespace => $namespace,
97     url => $self->option('url'),
98     user => $self->option('user'),
99     password => $self->option('password'),
100   );
101   
102   $prizm->$method(@_);
103 }
104
105 sub queued_prizm_command {  # subroutine
106   my( $url, $user, $password, $namespace, $method, @args ) = @_;
107
108   eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
109   die $@ if $@;
110
111   my $prizm = new Net::Prizm (
112     namespace => $namespace,
113     url => $url,
114     user => $user,
115     password => $password,
116   );
117   
118   $err_or_som = $prizm->$method( @args);
119
120   die $err_or_som
121     unless ref($err_or_som);
122
123   '';
124
125 }
126
127 sub _export_insert {
128   my( $self, $svc ) = ( shift, shift );
129   warn "$me: _export_insert called for export ". $self->exportnum.
130     " on service ". $svc->svcnum. "\n"
131     if $DEBUG;
132
133   my $cust_main = $svc->cust_svc->cust_pkg->cust_main;
134
135   my $err_or_som = $self->prizm_command('CustomerIfService', 'getCustomers',
136                                         ['import_id'],
137                                         [$cust_main->custnum],
138                                         ['='],
139                                        );
140   return $err_or_som
141     unless ref($err_or_som);
142
143   my $pre = '';
144   if ( defined $cust_main->dbdef_table->column('ship_last') ) {
145     $pre = $cust_main->ship_last ? 'ship_' : '';
146   }
147   my $name = $pre ? $cust_main->ship_name : $cust_main->name;
148   my $location = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
149                            qw (address1 address2 city state zip)
150                      );
151   my $contact = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
152                           qw (daytime night)
153                      );
154
155   my $pcustomer;
156   if ($err_or_som->result->[0]) {
157     $pcustomer = $err_or_som->result->[0]->customerId;
158     warn "$me: found customer $pcustomer in prizm\n" if $DEBUG;
159   }else{
160     my $chashref = $cust_main->hashref;
161     my $customerinfo = {
162       importId         => $cust_main->custnum,
163       customerName     => $name,
164       customerType     => 'freeside',
165       address1         => $chashref->{"${pre}address1"},
166       address2         => $chashref->{"${pre}address2"},
167       city             => $chashref->{"${pre}city"},
168       state            => $chashref->{"${pre}state"},
169       zipCode          => $chashref->{"${pre}zip"},
170       workPhone        => $chashref->{"${pre}daytime"},
171       homePhone        => $chashref->{"${pre}night"},
172       email            => @{[$cust_main->invoicing_list_emailonly]}[0],
173       extraFieldNames  => [ 'country', 'freesideId',
174                           ],
175       extraFieldValues => [ $chashref->{"${pre}country"}, $cust_main->custnum,
176                           ],
177     };
178
179     $err_or_som = $self->prizm_command('CustomerIfService', 'addCustomer',
180                                        $customerinfo);
181     return $err_or_som
182       unless ref($err_or_som);
183
184     $pcustomer = $err_or_som->result;
185     warn "$me: added customer $pcustomer to prizm\n" if $DEBUG;
186   }
187   warn "multiple prizm customers found for $cust_main->custnum"
188     if scalar(@$pcustomer) > 1;
189
190 #  #kinda big question/expensive
191 #  $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
192 #                                     ['Network Default Gateway Address'],
193 #                                     [$svc->addr_block->ip_gateway],
194 #                                     ['='],
195 #                   );
196 #  return $err_or_som
197 #    unless ref($err_or_som);
198 #
199 #  return "No elements in network" unless exists $err_or_som->result->[0];
200
201   my $networkid = 0;
202 #  for (my $i = 0; $i < $err_or_som->result->[0]->attributeNames; $i++) {
203 #    if ($err_or_som->result->[0]->attributeNames->[$i] eq "Network.ID"){
204 #      $networkid = $err_or_som->result->[0]->attributeValues->[$i];
205 #      last;
206 #    }
207 #  }
208
209   my $performance_profile = $svc->performance_profile;
210   $performance_profile ||= $svc->cust_svc->cust_pkg->part_pkg->pkg;
211
212   my $element_name_length = 50;
213   $element_name_length = $1
214     if $self->option('element_name_length') =~ /^\s*(\d+)\s*$/;
215   $err_or_som = $self->prizm_command('NetworkIfService', 'addProvisionedElement',
216                                       $networkid,
217                                       $svc->mac_addr,
218                                       substr($name . " " . $svc->description,
219                                              0, $element_name_length),
220                                       $location,
221                                       $contact,
222                                       sprintf("%032X", $svc->authkey || 0),
223                                       $performance_profile,
224                                       $svc->vlan_profile,
225                                       ($self->option('ems') ? 1 : 0 ),
226                                      );
227   return $err_or_som
228     unless ref($err_or_som);
229   warn "$me: added provisioned element to prizm\n" if $DEBUG;
230
231   my (@names) = ('Management IP',
232                  'GPS Latitude',
233                  'GPS Longitude',
234                  'GPS Altitude',
235                  'Site Name',
236                  'Site Location',
237                  'Site Contact',
238                  );
239   my (@values) = ($svc->ip_addr,
240                   $svc->latitude,
241                   $svc->longitude,
242                   $svc->altitude,
243                   $name . " " . $svc->description,
244                   $location,
245                   $contact,
246                   );
247   $element = $err_or_som->result->elementId;
248   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
249                                      [ $element ],
250                                      \@names,
251                                      \@values,
252                                      0,
253                                      1,
254                                     );
255   return $err_or_som
256     unless ref($err_or_som);
257   warn "$me: set element configuration\n" if $DEBUG;
258
259   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
260                                      [ $element ],
261                                      $svc->vlan_profile,
262                                      0,
263                                      1,
264                                     );
265   return $err_or_som
266     unless ref($err_or_som);
267   warn "$me: set element vlan profile\n" if $DEBUG;
268
269   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
270                                      [ $element ],
271                                      $performance_profile,
272                                      0,
273                                      1,
274                                     );
275   return $err_or_som
276     unless ref($err_or_som);
277   warn "$me: set element configset (performance profile)\n" if $DEBUG;
278
279   $err_or_som = $self->prizm_command('NetworkIfService',
280                                      'activateNetworkElements',
281                                      [ $element ],
282                                      1,
283                                      ( $self->option('ems') ? 1 : 0 ),
284                                     );
285
286   return $err_or_som
287     unless ref($err_or_som);
288   warn "$me: activated element\n" if $DEBUG;
289
290   $err_or_som = $self->prizm_command('CustomerIfService',
291                                      'addElementToCustomer',
292                                      0,
293                                      $cust_main->custnum,
294                                      0,
295                                      $svc->mac_addr,
296                                     );
297
298   return $err_or_som
299     unless ref($err_or_som);
300   warn "$me: added element to customer\n" if $DEBUG;
301
302   '';
303 }
304
305 sub _export_delete {
306   my( $self, $svc ) = ( shift, shift );
307
308   my $oldAutoCommit = $FS::UID::AutoCommit;
309   local $FS::UID::AutoCommit = 0;
310   my $dbh = dbh;
311
312   my $cust_pkg = $svc->cust_svc->cust_pkg;
313
314   my $depend = [];
315
316   if ($cust_pkg) {
317     my $queue = new FS::queue {
318       'svcnum' => $svc->svcnum,
319       'job'    => 'FS::part_export::prizm::queued_prizm_command',
320     };
321     my $error = $queue->insert(
322       ( map { $self->option($_) }
323             qw( url user password ) ),
324       'CustomerIfService',
325       'removeElementFromCustomer',
326       0,
327       $cust_pkg->custnum,
328       0,
329       $svc->mac_addr,
330     );
331
332     if ($error) {
333       $dbh->rollback if $oldAutoCommit;
334       return $error;
335     }
336
337     push @$depend, $queue->jobnum;
338   }
339
340   my $err_or_queue =
341     $self->queue_statuschange('deleteElement', $depend, $svc, 1);
342
343   unless (ref($err_or_queue)) {
344     $dbh->rollback if $oldAutoCommit;
345     return $err_or_queue;
346   }
347
348   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
349
350   '';
351 }
352
353 sub _export_replace {
354   my( $self, $new, $old ) = ( shift, shift, shift );
355
356   my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
357                                         [ 'MAC Address' ],
358                                         [ $old->mac_addr ],
359                                         [ '=' ],
360                                        );
361   return $err_or_som
362     unless ref($err_or_som);
363
364   return "Can't find prizm element for " . $old->mac_addr
365     unless $err_or_som->result->[0];
366
367   my %freeside2prizm = (  mac_addr     => 'MAC Address',
368                           ip_addr      => 'Management IP',
369                           latitude     => 'GPS Latitude',
370                           longitude    => 'GPS Longitude',
371                           altitude     => 'GPS Altitude',
372                           authkey      => 'Authentication Key',
373                        );
374   
375   my (@values);
376   my (@names) = map { push @values, $new->$_; $freeside2prizm{$_} }
377     grep { $old->$_ ne $new->$_ }
378       grep { exists($freeside2prizm{$_}) }
379         fields( 'svc_broadband' );
380
381   if ($old->description ne $new->description) {
382     my $cust_main = $old->cust_svc->cust_pkg->cust_main;
383     my $name = defined($cust_main->dbdef_table->column('ship_last'))
384              ? $cust_main->ship_name
385              : $cust_main->name;
386     push @values, $name . " " . $new->description;
387     push @names, "Site Name";
388   }
389
390   my $element = $err_or_som->result->[0]->elementId;
391
392   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
393                                         [ $element ],
394                                         \@names,
395                                         \@values,
396                                         0,
397                                         1,
398                                        );
399   return $err_or_som
400     unless ref($err_or_som);
401
402   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
403                                      [ $element ],
404                                      $new->vlan_profile,
405                                      0,
406                                      1,
407                                     )
408     if $old->vlan_profile ne $new->vlan_profile;
409
410   return $err_or_som
411     unless ref($err_or_som);
412
413   my $performance_profile = $new->performance_profile;
414   $performance_profile ||= $new->cust_svc->cust_pkg->part_pkg->pkg;
415
416   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
417                                      [ $element ],
418                                      $performance_profile,
419                                      0,
420                                      1,
421                                     );
422   return $err_or_som
423     unless ref($err_or_som);
424
425   '';
426
427 }
428
429 sub _export_suspend {
430   my( $self, $svc ) = ( shift, shift );
431   my $depend = [];
432   my $ems = $self->option('ems') ? 1 : 0;
433   my $err_or_queue = '';
434
435   my $oldAutoCommit = $FS::UID::AutoCommit;
436   local $FS::UID::AutoCommit = 0;
437   my $dbh = dbh;
438
439   $err_or_queue = 
440      $self->queue_statuschange('suspendNetworkElements', [], $svc, 1, $ems);
441   unless (ref($err_or_queue)) {
442     $dbh->rollback if $oldAutoCommit;
443     return $err_or_queue;
444   }
445   push @$depend, $err_or_queue->jobnum;
446
447   if ($ems && $self->option('always_bam')) {
448     $err_or_queue =
449       $self->queue_statuschange('suspendNetworkElements', $depend, $svc, 1, 0);
450     unless (ref($err_or_queue)) {
451       $dbh->rollback if $oldAutoCommit;
452       return $err_or_queue;
453     }
454   }
455
456   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
457
458   '';
459 }
460
461 sub _export_unsuspend {
462   my( $self, $svc ) = ( shift, shift );
463   my $depend = [];
464   my $ems = $self->option('ems') ? 1 : 0;
465   my $err_or_queue = '';
466
467   my $oldAutoCommit = $FS::UID::AutoCommit;
468   local $FS::UID::AutoCommit = 0;
469   my $dbh = dbh;
470
471   if ($ems && $self->option('always_bam')) {
472     $err_or_queue =
473       $self->queue_statuschange('activateNetworkElements', [], $svc, 1, 0);
474     unless (ref($err_or_queue)) {
475       $dbh->rollback if $oldAutoCommit;
476       return $err_or_queue;
477     }
478     push @$depend, $err_or_queue->jobnum;
479   }
480
481   $err_or_queue =
482     $self->queue_statuschange('activateNetworkElements', $depend, $svc, 1, $ems);
483   unless (ref($err_or_queue)) {
484     $dbh->rollback if $oldAutoCommit;
485     return $err_or_queue;
486   }
487
488   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
489
490   '';
491 }
492
493 sub export_links {
494   my( $self, $svc, $arrayref ) = ( shift, shift, shift );
495
496   push @$arrayref,
497     '<A HREF="http://'. $svc->ip_addr. '" target="_blank">SM</A>';
498
499   '';
500 }
501
502 sub queue_statuschange {
503   my( $self, $method, $jobs, $svc, @args ) = @_;
504
505   # already in a transaction and can't die here
506
507   my $queue = new FS::queue {
508     'svcnum' => $svc->svcnum,
509     'job'    => 'FS::part_export::prizm::statuschange',
510   };
511   my $error = $queue->insert(
512     ( map { $self->option($_) }
513           qw( url user password ) ),
514     $method,
515     $svc->mac_addr,
516     @args,
517   );
518
519   unless ($error) {                   # successful insertion
520     foreach my $job ( @$jobs ) {
521       $error ||= $queue->depend_insert($job);
522     }
523   }
524
525   $error or $queue;
526 }
527
528 sub statuschange {  # subroutine
529   my( $url, $user, $password, $method, $mac_addr, @args) = @_;
530
531   eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
532   die $@ if $@;
533
534   my $prizm = new Net::Prizm (
535     namespace => 'NetworkIfService',
536     url => $url,
537     user => $user,
538     password => $password,
539   );
540   
541   my $err_or_som = $prizm->getPrizmElements( [ 'MAC Address' ],
542                                              [ $mac_addr ],
543                                              [ '=' ],
544                                            );
545   die $err_or_som
546     unless ref($err_or_som);
547
548   die "Can't find prizm element for " . $mac_addr
549     unless $err_or_som->result->[0];
550
551   my $arg1;
552   # yuck!
553   if ($method =~ /suspendNetworkElements/ || $method =~ /activateNetworkElements/) {
554     $arg1 = [ $err_or_som->result->[0]->elementId ];
555   }else{
556     $arg1 = $err_or_som->result->[0]->elementId;
557   }
558   $err_or_som = $prizm->$method( $arg1, @args );
559
560   die $err_or_som
561     unless ref($err_or_som);
562
563   '';
564
565 }
566
567
568 1;