export host selection per service, RT#17914
[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   'no_machine' => 1,
87   'notes'      => $notes,
88 );
89
90 sub prizm_command {
91   my ($self,$namespace,$method) = (shift,shift,shift);
92
93   eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
94   die $@ if $@;
95
96   my $prizm = new Net::Prizm (
97     namespace => $namespace,
98     url => $self->option('url'),
99     user => $self->option('user'),
100     password => $self->option('password'),
101   );
102   
103   $prizm->$method(@_);
104 }
105
106 sub queued_prizm_command {  # subroutine
107   my( $url, $user, $password, $namespace, $method, @args ) = @_;
108
109   eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
110   die $@ if $@;
111
112   my $prizm = new Net::Prizm (
113     namespace => $namespace,
114     url => $url,
115     user => $user,
116     password => $password,
117   );
118   
119   $err_or_som = $prizm->$method( @args);
120
121   die $err_or_som
122     unless ref($err_or_som);
123
124   '';
125
126 }
127
128 sub _export_insert {
129   my( $self, $svc ) = ( shift, shift );
130   warn "$me: _export_insert called for export ". $self->exportnum.
131     " on service ". $svc->svcnum. "\n"
132     if $DEBUG;
133
134   my $cust_main = $svc->cust_svc->cust_pkg->cust_main;
135
136   my $err_or_som = $self->prizm_command('CustomerIfService', 'getCustomers',
137                                         ['import_id'],
138                                         [$cust_main->custnum],
139                                         ['='],
140                                        );
141   return $err_or_som
142     unless ref($err_or_som);
143
144   my $pre = '';
145   if ( defined $cust_main->dbdef_table->column('ship_last') ) {
146     $pre = $cust_main->ship_last ? 'ship_' : '';
147   }
148   my $name = $pre ? $cust_main->ship_name : $cust_main->name;
149   my $location = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
150                            qw (address1 address2 city state zip)
151                      );
152   my $contact = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
153                           qw (daytime night)
154                      );
155
156   my $pcustomer;
157   if ($err_or_som->result->[0]) {
158     $pcustomer = $err_or_som->result->[0]->customerId;
159     warn "$me: found customer $pcustomer in prizm\n" if $DEBUG;
160   }else{
161     my $chashref = $cust_main->hashref;
162     my $customerinfo = {
163       importId         => $cust_main->custnum,
164       customerName     => $name,
165       customerType     => 'freeside',
166       address1         => $chashref->{"${pre}address1"},
167       address2         => $chashref->{"${pre}address2"},
168       city             => $chashref->{"${pre}city"},
169       state            => $chashref->{"${pre}state"},
170       zipCode          => $chashref->{"${pre}zip"},
171       workPhone        => $chashref->{"${pre}daytime"},
172       homePhone        => $chashref->{"${pre}night"},
173       email            => @{[$cust_main->invoicing_list_emailonly]}[0],
174       extraFieldNames  => [ 'country', 'freesideId',
175                           ],
176       extraFieldValues => [ $chashref->{"${pre}country"}, $cust_main->custnum,
177                           ],
178     };
179
180     $err_or_som = $self->prizm_command('CustomerIfService', 'addCustomer',
181                                        $customerinfo);
182     return $err_or_som
183       unless ref($err_or_som);
184
185     $pcustomer = $err_or_som->result;
186     warn "$me: added customer $pcustomer to prizm\n" if $DEBUG;
187   }
188   warn "multiple prizm customers found for $cust_main->custnum"
189     if scalar(@$pcustomer) > 1;
190
191 #  #kinda big question/expensive
192 #  $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
193 #                                     ['Network Default Gateway Address'],
194 #                                     [$svc->addr_block->ip_gateway],
195 #                                     ['='],
196 #                   );
197 #  return $err_or_som
198 #    unless ref($err_or_som);
199 #
200 #  return "No elements in network" unless exists $err_or_som->result->[0];
201
202   my $networkid = 0;
203 #  for (my $i = 0; $i < $err_or_som->result->[0]->attributeNames; $i++) {
204 #    if ($err_or_som->result->[0]->attributeNames->[$i] eq "Network.ID"){
205 #      $networkid = $err_or_som->result->[0]->attributeValues->[$i];
206 #      last;
207 #    }
208 #  }
209
210 # here we cope with a problem of prizm failing to insert for reason
211 # of duplicate mac addr, but doing so inconsistently... a race in prizm?
212
213   $self->prizm_command( 'CustomerIfService', 'removeElementFromCustomer',
214                         0,
215                         $cust_main->custnum,
216                         0,
217                         $svc->mac_addr,
218                       );
219
220   $err_or_som = $self->prizm_command( 'NetworkIfService', 'getPrizmElements',
221                                       [ 'MAC Address' ],
222                                       [ $svc->mac_addr ],
223                                       [ '=' ],
224                                     );
225   if ( ref($err_or_som) && $err_or_som->result->[0] ) { # ignore errors
226     $self->prizm_command( 'NetworkIfService', 'deleteElement',
227                           $err_or_som->result->[0],
228                           1,
229                         );
230   }
231 # end of coping
232
233   my $performance_profile = $svc->performance_profile;
234   $performance_profile ||= $svc->cust_svc->cust_pkg->part_pkg->pkg;
235
236   my $element_name_length = 50;
237   $element_name_length = $1
238     if $self->option('element_name_length') =~ /^\s*(\d+)\s*$/;
239   $err_or_som = $self->prizm_command('NetworkIfService', 'addProvisionedElement',
240                                       $networkid,
241                                       $svc->mac_addr,
242                                       substr($name . " " . $svc->description,
243                                              0, $element_name_length),
244                                       $location,
245                                       $contact,
246                                       sprintf("%032X", $svc->authkey || 0),
247                                       $performance_profile,
248                                       $svc->vlan_profile,
249                                       ($self->option('ems') ? 1 : 0 ),
250                                      );
251   return $err_or_som
252     unless ref($err_or_som);
253   warn "$me: added provisioned element to prizm\n" if $DEBUG;
254
255   my (@names) = ('Management IP',
256                  'GPS Latitude',
257                  'GPS Longitude',
258                  'GPS Altitude',
259                  'Site Name',
260                  'Site Location',
261                  'Site Contact',
262                  );
263   my (@values) = ($svc->ip_addr,
264                   $svc->latitude,
265                   $svc->longitude,
266                   $svc->altitude,
267                   $name . " " . $svc->description,
268                   $location,
269                   $contact,
270                   );
271   $element = $err_or_som->result->elementId;
272   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
273                                      [ $element ],
274                                      \@names,
275                                      \@values,
276                                      0,
277                                      1,
278                                     );
279   return $err_or_som
280     unless ref($err_or_som);
281   warn "$me: set element configuration\n" if $DEBUG;
282
283   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
284                                      [ $element ],
285                                      $svc->vlan_profile,
286                                      0,
287                                      1,
288                                     );
289   return $err_or_som
290     unless ref($err_or_som);
291   warn "$me: set element vlan profile\n" if $DEBUG;
292
293   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
294                                      [ $element ],
295                                      $performance_profile,
296                                      0,
297                                      1,
298                                     );
299   return $err_or_som
300     unless ref($err_or_som);
301   warn "$me: set element configset (performance profile)\n" if $DEBUG;
302
303   $err_or_som = $self->prizm_command('NetworkIfService',
304                                      'activateNetworkElements',
305                                      [ $element ],
306                                      1,
307                                      ( $self->option('ems') ? 1 : 0 ),
308                                     );
309
310   return $err_or_som
311     unless ref($err_or_som);
312   warn "$me: activated element\n" if $DEBUG;
313
314   $err_or_som = $self->prizm_command('CustomerIfService',
315                                      'addElementToCustomer',
316                                      0,
317                                      $cust_main->custnum,
318                                      0,
319                                      $svc->mac_addr,
320                                     );
321
322   return $err_or_som
323     unless ref($err_or_som);
324   warn "$me: added element to customer\n" if $DEBUG;
325
326   '';
327 }
328
329 sub _export_delete {
330   my( $self, $svc ) = ( shift, shift );
331
332   my $oldAutoCommit = $FS::UID::AutoCommit;
333   local $FS::UID::AutoCommit = 0;
334   my $dbh = dbh;
335
336   my $cust_pkg = $svc->cust_svc->cust_pkg;
337
338   my $depend = [];
339
340   if ($cust_pkg) {
341     my $queue = new FS::queue {
342       'svcnum' => $svc->svcnum,
343       'job'    => 'FS::part_export::prizm::queued_prizm_command',
344     };
345     my $error = $queue->insert(
346       ( map { $self->option($_) }
347             qw( url user password ) ),
348       'CustomerIfService',
349       'removeElementFromCustomer',
350       0,
351       $cust_pkg->custnum,
352       0,
353       $svc->mac_addr,
354     );
355
356     if ($error) {
357       $dbh->rollback if $oldAutoCommit;
358       return $error;
359     }
360
361     push @$depend, $queue->jobnum;
362   }
363
364   my $err_or_queue =
365     $self->queue_statuschange('deleteElement', $depend, $svc, 1);
366
367   unless (ref($err_or_queue)) {
368     $dbh->rollback if $oldAutoCommit;
369     return $err_or_queue;
370   }
371
372   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
373
374   '';
375 }
376
377 sub _export_replace {
378   my( $self, $new, $old ) = ( shift, shift, shift );
379
380   my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
381                                         [ 'MAC Address' ],
382                                         [ $old->mac_addr ],
383                                         [ '=' ],
384                                        );
385   return $err_or_som
386     unless ref($err_or_som);
387
388   return "Can't find prizm element for " . $old->mac_addr
389     unless $err_or_som->result->[0];
390
391   my %freeside2prizm = (  mac_addr     => 'MAC Address',
392                           ip_addr      => 'Management IP',
393                           latitude     => 'GPS Latitude',
394                           longitude    => 'GPS Longitude',
395                           altitude     => 'GPS Altitude',
396                           authkey      => 'Authentication Key',
397                        );
398   
399   my (@values);
400   my (@names) = map { push @values, $new->$_; $freeside2prizm{$_} }
401     grep { $old->$_ ne $new->$_ }
402       grep { exists($freeside2prizm{$_}) }
403         fields( 'svc_broadband' );
404
405   if ($old->description ne $new->description) {
406     my $cust_main = $old->cust_svc->cust_pkg->cust_main;
407     my $name = defined($cust_main->dbdef_table->column('ship_last'))
408              ? $cust_main->ship_name
409              : $cust_main->name;
410     push @values, $name . " " . $new->description;
411     push @names, "Site Name";
412   }
413
414   my $element = $err_or_som->result->[0]->elementId;
415
416   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
417                                         [ $element ],
418                                         \@names,
419                                         \@values,
420                                         0,
421                                         1,
422                                        );
423   return $err_or_som
424     unless ref($err_or_som);
425
426   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
427                                      [ $element ],
428                                      $new->vlan_profile,
429                                      0,
430                                      1,
431                                     )
432     if $old->vlan_profile ne $new->vlan_profile;
433
434   return $err_or_som
435     unless ref($err_or_som);
436
437   my $performance_profile = $new->performance_profile;
438   $performance_profile ||= $new->cust_svc->cust_pkg->part_pkg->pkg;
439
440   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
441                                      [ $element ],
442                                      $performance_profile,
443                                      0,
444                                      1,
445                                     );
446   return $err_or_som
447     unless ref($err_or_som);
448
449   '';
450
451 }
452
453 sub _export_suspend {
454   my( $self, $svc ) = ( shift, shift );
455   my $depend = [];
456   my $ems = $self->option('ems') ? 1 : 0;
457   my $err_or_queue = '';
458
459   my $oldAutoCommit = $FS::UID::AutoCommit;
460   local $FS::UID::AutoCommit = 0;
461   my $dbh = dbh;
462
463   $err_or_queue = 
464      $self->queue_statuschange('suspendNetworkElements', [], $svc, 1, $ems);
465   unless (ref($err_or_queue)) {
466     $dbh->rollback if $oldAutoCommit;
467     return $err_or_queue;
468   }
469   push @$depend, $err_or_queue->jobnum;
470
471   if ($ems && $self->option('always_bam')) {
472     $err_or_queue =
473       $self->queue_statuschange('suspendNetworkElements', $depend, $svc, 1, 0);
474     unless (ref($err_or_queue)) {
475       $dbh->rollback if $oldAutoCommit;
476       return $err_or_queue;
477     }
478   }
479
480   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
481
482   '';
483 }
484
485 sub _export_unsuspend {
486   my( $self, $svc ) = ( shift, shift );
487   my $depend = [];
488   my $ems = $self->option('ems') ? 1 : 0;
489   my $err_or_queue = '';
490
491   my $oldAutoCommit = $FS::UID::AutoCommit;
492   local $FS::UID::AutoCommit = 0;
493   my $dbh = dbh;
494
495   if ($ems && $self->option('always_bam')) {
496     $err_or_queue =
497       $self->queue_statuschange('activateNetworkElements', [], $svc, 1, 0);
498     unless (ref($err_or_queue)) {
499       $dbh->rollback if $oldAutoCommit;
500       return $err_or_queue;
501     }
502     push @$depend, $err_or_queue->jobnum;
503   }
504
505   $err_or_queue =
506     $self->queue_statuschange('activateNetworkElements', $depend, $svc, 1, $ems);
507   unless (ref($err_or_queue)) {
508     $dbh->rollback if $oldAutoCommit;
509     return $err_or_queue;
510   }
511
512   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
513
514   '';
515 }
516
517 sub export_links {
518   my( $self, $svc, $arrayref ) = ( shift, shift, shift );
519
520   push @$arrayref,
521     '<A HREF="http://'. $svc->ip_addr. '" target="_blank">SM</A>';
522
523   '';
524 }
525
526 sub queue_statuschange {
527   my( $self, $method, $jobs, $svc, @args ) = @_;
528
529   # already in a transaction and can't die here
530
531   my $queue = new FS::queue {
532     'svcnum' => $svc->svcnum,
533     'job'    => 'FS::part_export::prizm::statuschange',
534   };
535   my $error = $queue->insert(
536     ( map { $self->option($_) }
537           qw( url user password ) ),
538     $method,
539     $svc->mac_addr,
540     @args,
541   );
542
543   unless ($error) {                   # successful insertion
544     foreach my $job ( @$jobs ) {
545       $error ||= $queue->depend_insert($job);
546     }
547   }
548
549   $error or $queue;
550 }
551
552 sub statuschange {  # subroutine
553   my( $url, $user, $password, $method, $mac_addr, @args) = @_;
554
555   eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
556   die $@ if $@;
557
558   my $prizm = new Net::Prizm (
559     namespace => 'NetworkIfService',
560     url => $url,
561     user => $user,
562     password => $password,
563   );
564   
565   my $err_or_som = $prizm->getPrizmElements( [ 'MAC Address' ],
566                                              [ $mac_addr ],
567                                              [ '=' ],
568                                            );
569   die $err_or_som
570     unless ref($err_or_som);
571
572   die "Can't find prizm element for " . $mac_addr
573     unless $err_or_som->result->[0];
574
575   my $arg1;
576   # yuck!
577   if ($method =~ /suspendNetworkElements/ || $method =~ /activateNetworkElements/) {
578     $arg1 = [ $err_or_som->result->[0]->elementId ];
579   }else{
580     $arg1 = $err_or_som->result->[0]->elementId;
581   }
582   $err_or_som = $prizm->$method( $arg1, @args );
583
584   die $err_or_som
585     unless ref($err_or_som);
586
587   '';
588
589 }
590
591
592 1;