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