This commit was generated by cvs2svn to compensate for changes in r11022,
[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 # here we cope with a problem of prizm failing to insert for reason
210 # of duplicate mac addr, but doing so inconsistently... a race in prizm?
211
212   $self->prizm_command( 'CustomerIfService', 'removeElementFromCustomer',
213                         0,
214                         $cust_main->custnum,
215                         0,
216                         $svc->mac_addr,
217                       );
218
219   $err_or_som = $self->prizm_command( 'NetworkIfService', 'getPrizmElements',
220                                       [ 'MAC Address' ],
221                                       [ $svc->mac_addr ],
222                                       [ '=' ],
223                                     );
224   if ( ref($err_or_som) && $err_or_som->result->[0] ) { # ignore errors
225     $self->prizm_command( 'NetworkIfService', 'deleteElement',
226                           $err_or_som->result->[0],
227                           1,
228                         );
229   }
230 # end of coping
231
232   my $performance_profile = $svc->performance_profile;
233   $performance_profile ||= $svc->cust_svc->cust_pkg->part_pkg->pkg;
234
235   my $element_name_length = 50;
236   $element_name_length = $1
237     if $self->option('element_name_length') =~ /^\s*(\d+)\s*$/;
238   $err_or_som = $self->prizm_command('NetworkIfService', 'addProvisionedElement',
239                                       $networkid,
240                                       $svc->mac_addr,
241                                       substr($name . " " . $svc->description,
242                                              0, $element_name_length),
243                                       $location,
244                                       $contact,
245                                       sprintf("%032X", $svc->authkey || 0),
246                                       $performance_profile,
247                                       $svc->vlan_profile,
248                                       ($self->option('ems') ? 1 : 0 ),
249                                      );
250   return $err_or_som
251     unless ref($err_or_som);
252   warn "$me: added provisioned element to prizm\n" if $DEBUG;
253
254   my (@names) = ('Management IP',
255                  'GPS Latitude',
256                  'GPS Longitude',
257                  'GPS Altitude',
258                  'Site Name',
259                  'Site Location',
260                  'Site Contact',
261                  );
262   my (@values) = ($svc->ip_addr,
263                   $svc->latitude,
264                   $svc->longitude,
265                   $svc->altitude,
266                   $name . " " . $svc->description,
267                   $location,
268                   $contact,
269                   );
270   $element = $err_or_som->result->elementId;
271   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
272                                      [ $element ],
273                                      \@names,
274                                      \@values,
275                                      0,
276                                      1,
277                                     );
278   return $err_or_som
279     unless ref($err_or_som);
280   warn "$me: set element configuration\n" if $DEBUG;
281
282   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
283                                      [ $element ],
284                                      $svc->vlan_profile,
285                                      0,
286                                      1,
287                                     );
288   return $err_or_som
289     unless ref($err_or_som);
290   warn "$me: set element vlan profile\n" if $DEBUG;
291
292   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
293                                      [ $element ],
294                                      $performance_profile,
295                                      0,
296                                      1,
297                                     );
298   return $err_or_som
299     unless ref($err_or_som);
300   warn "$me: set element configset (performance profile)\n" if $DEBUG;
301
302   $err_or_som = $self->prizm_command('NetworkIfService',
303                                      'activateNetworkElements',
304                                      [ $element ],
305                                      1,
306                                      ( $self->option('ems') ? 1 : 0 ),
307                                     );
308
309   return $err_or_som
310     unless ref($err_or_som);
311   warn "$me: activated element\n" if $DEBUG;
312
313   $err_or_som = $self->prizm_command('CustomerIfService',
314                                      'addElementToCustomer',
315                                      0,
316                                      $cust_main->custnum,
317                                      0,
318                                      $svc->mac_addr,
319                                     );
320
321   return $err_or_som
322     unless ref($err_or_som);
323   warn "$me: added element to customer\n" if $DEBUG;
324
325   '';
326 }
327
328 sub _export_delete {
329   my( $self, $svc ) = ( shift, shift );
330
331   my $oldAutoCommit = $FS::UID::AutoCommit;
332   local $FS::UID::AutoCommit = 0;
333   my $dbh = dbh;
334
335   my $cust_pkg = $svc->cust_svc->cust_pkg;
336
337   my $depend = [];
338
339   if ($cust_pkg) {
340     my $queue = new FS::queue {
341       'svcnum' => $svc->svcnum,
342       'job'    => 'FS::part_export::prizm::queued_prizm_command',
343     };
344     my $error = $queue->insert(
345       ( map { $self->option($_) }
346             qw( url user password ) ),
347       'CustomerIfService',
348       'removeElementFromCustomer',
349       0,
350       $cust_pkg->custnum,
351       0,
352       $svc->mac_addr,
353     );
354
355     if ($error) {
356       $dbh->rollback if $oldAutoCommit;
357       return $error;
358     }
359
360     push @$depend, $queue->jobnum;
361   }
362
363   my $err_or_queue =
364     $self->queue_statuschange('deleteElement', $depend, $svc, 1);
365
366   unless (ref($err_or_queue)) {
367     $dbh->rollback if $oldAutoCommit;
368     return $err_or_queue;
369   }
370
371   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
372
373   '';
374 }
375
376 sub _export_replace {
377   my( $self, $new, $old ) = ( shift, shift, shift );
378
379   my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
380                                         [ 'MAC Address' ],
381                                         [ $old->mac_addr ],
382                                         [ '=' ],
383                                        );
384   return $err_or_som
385     unless ref($err_or_som);
386
387   return "Can't find prizm element for " . $old->mac_addr
388     unless $err_or_som->result->[0];
389
390   my %freeside2prizm = (  mac_addr     => 'MAC Address',
391                           ip_addr      => 'Management IP',
392                           latitude     => 'GPS Latitude',
393                           longitude    => 'GPS Longitude',
394                           altitude     => 'GPS Altitude',
395                           authkey      => 'Authentication Key',
396                        );
397   
398   my (@values);
399   my (@names) = map { push @values, $new->$_; $freeside2prizm{$_} }
400     grep { $old->$_ ne $new->$_ }
401       grep { exists($freeside2prizm{$_}) }
402         fields( 'svc_broadband' );
403
404   if ($old->description ne $new->description) {
405     my $cust_main = $old->cust_svc->cust_pkg->cust_main;
406     my $name = defined($cust_main->dbdef_table->column('ship_last'))
407              ? $cust_main->ship_name
408              : $cust_main->name;
409     push @values, $name . " " . $new->description;
410     push @names, "Site Name";
411   }
412
413   my $element = $err_or_som->result->[0]->elementId;
414
415   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
416                                         [ $element ],
417                                         \@names,
418                                         \@values,
419                                         0,
420                                         1,
421                                        );
422   return $err_or_som
423     unless ref($err_or_som);
424
425   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
426                                      [ $element ],
427                                      $new->vlan_profile,
428                                      0,
429                                      1,
430                                     )
431     if $old->vlan_profile ne $new->vlan_profile;
432
433   return $err_or_som
434     unless ref($err_or_som);
435
436   my $performance_profile = $new->performance_profile;
437   $performance_profile ||= $new->cust_svc->cust_pkg->part_pkg->pkg;
438
439   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
440                                      [ $element ],
441                                      $performance_profile,
442                                      0,
443                                      1,
444                                     );
445   return $err_or_som
446     unless ref($err_or_som);
447
448   '';
449
450 }
451
452 sub _export_suspend {
453   my( $self, $svc ) = ( shift, shift );
454   my $depend = [];
455   my $ems = $self->option('ems') ? 1 : 0;
456   my $err_or_queue = '';
457
458   my $oldAutoCommit = $FS::UID::AutoCommit;
459   local $FS::UID::AutoCommit = 0;
460   my $dbh = dbh;
461
462   $err_or_queue = 
463      $self->queue_statuschange('suspendNetworkElements', [], $svc, 1, $ems);
464   unless (ref($err_or_queue)) {
465     $dbh->rollback if $oldAutoCommit;
466     return $err_or_queue;
467   }
468   push @$depend, $err_or_queue->jobnum;
469
470   if ($ems && $self->option('always_bam')) {
471     $err_or_queue =
472       $self->queue_statuschange('suspendNetworkElements', $depend, $svc, 1, 0);
473     unless (ref($err_or_queue)) {
474       $dbh->rollback if $oldAutoCommit;
475       return $err_or_queue;
476     }
477   }
478
479   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
480
481   '';
482 }
483
484 sub _export_unsuspend {
485   my( $self, $svc ) = ( shift, shift );
486   my $depend = [];
487   my $ems = $self->option('ems') ? 1 : 0;
488   my $err_or_queue = '';
489
490   my $oldAutoCommit = $FS::UID::AutoCommit;
491   local $FS::UID::AutoCommit = 0;
492   my $dbh = dbh;
493
494   if ($ems && $self->option('always_bam')) {
495     $err_or_queue =
496       $self->queue_statuschange('activateNetworkElements', [], $svc, 1, 0);
497     unless (ref($err_or_queue)) {
498       $dbh->rollback if $oldAutoCommit;
499       return $err_or_queue;
500     }
501     push @$depend, $err_or_queue->jobnum;
502   }
503
504   $err_or_queue =
505     $self->queue_statuschange('activateNetworkElements', $depend, $svc, 1, $ems);
506   unless (ref($err_or_queue)) {
507     $dbh->rollback if $oldAutoCommit;
508     return $err_or_queue;
509   }
510
511   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
512
513   '';
514 }
515
516 sub export_links {
517   my( $self, $svc, $arrayref ) = ( shift, shift, shift );
518
519   push @$arrayref,
520     '<A HREF="http://'. $svc->ip_addr. '" target="_blank">SM</A>';
521
522   '';
523 }
524
525 sub queue_statuschange {
526   my( $self, $method, $jobs, $svc, @args ) = @_;
527
528   # already in a transaction and can't die here
529
530   my $queue = new FS::queue {
531     'svcnum' => $svc->svcnum,
532     'job'    => 'FS::part_export::prizm::statuschange',
533   };
534   my $error = $queue->insert(
535     ( map { $self->option($_) }
536           qw( url user password ) ),
537     $method,
538     $svc->mac_addr,
539     @args,
540   );
541
542   unless ($error) {                   # successful insertion
543     foreach my $job ( @$jobs ) {
544       $error ||= $queue->depend_insert($job);
545     }
546   }
547
548   $error or $queue;
549 }
550
551 sub statuschange {  # subroutine
552   my( $url, $user, $password, $method, $mac_addr, @args) = @_;
553
554   eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
555   die $@ if $@;
556
557   my $prizm = new Net::Prizm (
558     namespace => 'NetworkIfService',
559     url => $url,
560     user => $user,
561     password => $password,
562   );
563   
564   my $err_or_som = $prizm->getPrizmElements( [ 'MAC Address' ],
565                                              [ $mac_addr ],
566                                              [ '=' ],
567                                            );
568   die $err_or_som
569     unless ref($err_or_som);
570
571   die "Can't find prizm element for " . $mac_addr
572     unless $err_or_som->result->[0];
573
574   my $arg1;
575   # yuck!
576   if ($method =~ /suspendNetworkElements/ || $method =~ /activateNetworkElements/) {
577     $arg1 = [ $err_or_som->result->[0]->elementId ];
578   }else{
579     $arg1 = $err_or_som->result->[0]->elementId;
580   }
581   $err_or_som = $prizm->$method( $arg1, @args );
582
583   die $err_or_som
584     unless ref($err_or_som);
585
586   '';
587
588 }
589
590
591 1;