export host selection per service, RT#17914
[freeside.git] / FS / FS / part_export / prizm.pm
index 92f1aec..9964489 100644 (file)
@@ -1,31 +1,96 @@
 package FS::part_export::prizm;
 
-use vars qw(@ISA %info %options $DEBUG);
+use vars qw(@ISA %info %options $DEBUG $me);
 use Tie::IxHash;
-use FS::Record qw(fields);
+use FS::Record qw(fields dbh);
 use FS::part_export;
 
 @ISA = qw(FS::part_export);
-$DEBUG = 1;
+$DEBUG = 0;
+$me = '[' . __PACKAGE__ . ']';
 
 tie %options, 'Tie::IxHash',
   'url'      => { label => 'Northbound url', default=>'https://localhost:8443/prizm/nbi' },
   'user'     => { label => 'Northbound username', default=>'nbi' },
   'password' => { label => 'Password', default => '' },
+  'ems'      => { label => 'Full EMS', type => 'checkbox' },
+  'always_bam' => { label => 'Always activate/suspend authentication', type => 'checkbox' },
+  'element_name_length' => { label => 'Size of siteName (best left blank)' },
 ;
 
+my $notes = <<'EOT';
+Real-time export of <b>svc_broadband</b>, <b>cust_pkg</b>, and <b>cust_main</b>
+record data to Motorola
+<a href="http://motorola.canopywireless.com/products/prizm/">Canopy Prizm
+software</a> via the Northbound interface.<br><br>
+
+Freeside will attempt to create an element in an existing network with the
+values provided in svc_broadband.  Of particular interest are
+<ul>
+  <li> mac address - used to identify the element
+  <li> vlan profile - an exact match for a vlan profiles defined in prizm
+  <li> ip address - defines the management ip address of the prizm element
+  <li> latitude - GPS latitude
+  <li> longitude - GPS longitude
+  <li> altitude - GPS altitude
+</ul>
+
+In addition freeside attempts to set the service plan name in prizm to the
+name of the package in which the service resides.
+
+The service is associated with a customer in prizm as well, and freeside
+will create the customer should none already exist with import id matching
+the freeside customer number.  The following fields are set.
+
+<ul>
+  <li> importId - the freeside customer number
+  <li> customerType - freeside
+  <li> customerName - the name associated with the freeside shipping address
+  <li> address1 - the shipping address
+  <li> address2
+  <li> city
+  <li> state
+  <li> zipCode
+  <li> country
+  <li> workPhone - the daytime phone number
+  <li> homePhone - the night phone number
+  <li> freesideId - the freeside customer number
+</ul>
+
+  Additionally set on the element are
+<ul>
+  <li> Site Name - The shipping name followed by the service broadband description field
+  <li> Site Location - the shipping address
+  <li> Site Contact - the daytime and night phone numbers
+</ul>
+
+Freeside provisions, suspends, and unsuspends elements BAM only unless the
+'Full EMS' checkbox is checked.<br><br>
+
+When freeside provisions an element the siteName is copied internally by
+prizm in such a manner that it is possible for the value to exceed the size
+of the column used in the prizm database.  Therefore freeside truncates
+by default this value to 50 characters.  It is thought that this
+column is the account_name column of the element_user_account table.  It
+may be possible to lift this limit by modifying the prizm database and
+setting a new appropriate value on this export.  This is untested and
+possibly harmful.
+
+EOT
+
 %info = (
-  'svc'      => 'svc_broadband',
-  'desc'     => 'Real-time export to Northbound Interface',
-  'options'  => \%options,
-  'nodomain' => 'Y',
-  'notes'    => 'These are notes.'
+  'svc'        => 'svc_broadband',
+  'desc'       => 'Real-time export to Northbound Interface',
+  'options'    => \%options,
+  'nodomain'   => 'Y',
+  'no_machine' => 1,
+  'notes'      => $notes,
 );
 
 sub prizm_command {
   my ($self,$namespace,$method) = (shift,shift,shift);
 
-  eval "use Net::Prizm qw(CustomerInfo PrizmElement);";
+  eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
   die $@ if $@;
 
   my $prizm = new Net::Prizm (
@@ -38,12 +103,37 @@ sub prizm_command {
   $prizm->$method(@_);
 }
 
+sub queued_prizm_command {  # subroutine
+  my( $url, $user, $password, $namespace, $method, @args ) = @_;
+
+  eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
+  die $@ if $@;
+
+  my $prizm = new Net::Prizm (
+    namespace => $namespace,
+    url => $url,
+    user => $user,
+    password => $password,
+  );
+  
+  $err_or_som = $prizm->$method( @args);
+
+  die $err_or_som
+    unless ref($err_or_som);
+
+  '';
+
+}
+
 sub _export_insert {
   my( $self, $svc ) = ( shift, shift );
+  warn "$me: _export_insert called for export ". $self->exportnum.
+    " on service ". $svc->svcnum. "\n"
+    if $DEBUG;
 
   my $cust_main = $svc->cust_svc->cust_pkg->cust_main;
 
-  my $err_or_som = $self->prizm_command(CustomerIfService, 'getCustomers',
+  my $err_or_som = $self->prizm_command('CustomerIfService', 'getCustomers',
                                         ['import_id'],
                                         [$cust_main->custnum],
                                         ['='],
@@ -66,6 +156,7 @@ sub _export_insert {
   my $pcustomer;
   if ($err_or_som->result->[0]) {
     $pcustomer = $err_or_som->result->[0]->customerId;
+    warn "$me: found customer $pcustomer in prizm\n" if $DEBUG;
   }else{
     my $chashref = $cust_main->hashref;
     my $customerinfo = {
@@ -92,42 +183,74 @@ sub _export_insert {
       unless ref($err_or_som);
 
     $pcustomer = $err_or_som->result;
+    warn "$me: added customer $pcustomer to prizm\n" if $DEBUG;
   }
   warn "multiple prizm customers found for $cust_main->custnum"
     if scalar(@$pcustomer) > 1;
 
-  #kinda big question/expensive
-  $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
-                                     ['Network Default Gateway Address'],
-                                     [$svc->addr_block->ip_gateway],
-                                     ['='],
-                   );
-  return $err_or_som
-    unless ref($err_or_som);
-
-  return "No elements in network" unless exists $err_or_som->result->[0];
+#  #kinda big question/expensive
+#  $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
+#                                     ['Network Default Gateway Address'],
+#                                     [$svc->addr_block->ip_gateway],
+#                                     ['='],
+#                   );
+#  return $err_or_som
+#    unless ref($err_or_som);
+#
+#  return "No elements in network" unless exists $err_or_som->result->[0];
 
   my $networkid = 0;
-  for (my $i = 0; $i < $err_or_som->result->[0]->attributeNames; $i++) {
-    if ($err_or_som->result->[0]->attributeNames->[$i] eq "Network.ID"){
-      $networkid = $err_or_som->result->[0]->attributeValues->[$i];
-      last;
-    }
+#  for (my $i = 0; $i < $err_or_som->result->[0]->attributeNames; $i++) {
+#    if ($err_or_som->result->[0]->attributeNames->[$i] eq "Network.ID"){
+#      $networkid = $err_or_som->result->[0]->attributeValues->[$i];
+#      last;
+#    }
+#  }
+
+# here we cope with a problem of prizm failing to insert for reason
+# of duplicate mac addr, but doing so inconsistently... a race in prizm?
+
+  $self->prizm_command( 'CustomerIfService', 'removeElementFromCustomer',
+                        0,
+                        $cust_main->custnum,
+                        0,
+                        $svc->mac_addr,
+                      );
+
+  $err_or_som = $self->prizm_command( 'NetworkIfService', 'getPrizmElements',
+                                      [ 'MAC Address' ],
+                                      [ $svc->mac_addr ],
+                                      [ '=' ],
+                                    );
+  if ( ref($err_or_som) && $err_or_som->result->[0] ) { # ignore errors
+    $self->prizm_command( 'NetworkIfService', 'deleteElement',
+                          $err_or_som->result->[0],
+                          1,
+                        );
   }
+# end of coping
+
+  my $performance_profile = $svc->performance_profile;
+  $performance_profile ||= $svc->cust_svc->cust_pkg->part_pkg->pkg;
 
+  my $element_name_length = 50;
+  $element_name_length = $1
+    if $self->option('element_name_length') =~ /^\s*(\d+)\s*$/;
   $err_or_som = $self->prizm_command('NetworkIfService', 'addProvisionedElement',
                                       $networkid,
                                       $svc->mac_addr,
-                                      $name,
+                                      substr($name . " " . $svc->description,
+                                             0, $element_name_length),
                                       $location,
                                       $contact,
-                                      sprintf("%032X", $svc->authkey),
-                                      $svc->cust_svc->cust_pkg->part_pkg->pkg,
+                                      sprintf("%032X", $svc->authkey || 0),
+                                      $performance_profile,
                                       $svc->vlan_profile,
-                                      1,
+                                      ($self->option('ems') ? 1 : 0 ),
                                      );
   return $err_or_som
     unless ref($err_or_som);
+  warn "$me: added provisioned element to prizm\n" if $DEBUG;
 
   my (@names) = ('Management IP',
                  'GPS Latitude',
@@ -141,7 +264,7 @@ sub _export_insert {
                   $svc->latitude,
                   $svc->longitude,
                   $svc->altitude,
-                  $name,
+                  $name . " " . $svc->description,
                   $location,
                   $contact,
                   );
@@ -155,6 +278,7 @@ sub _export_insert {
                                     );
   return $err_or_som
     unless ref($err_or_som);
+  warn "$me: set element configuration\n" if $DEBUG;
 
   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
                                      [ $element ],
@@ -164,25 +288,28 @@ sub _export_insert {
                                     );
   return $err_or_som
     unless ref($err_or_som);
+  warn "$me: set element vlan profile\n" if $DEBUG;
 
   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
                                      [ $element ],
-                                     $svc->cust_svc->cust_pkg->part_pkg->pkg,
+                                     $performance_profile,
                                      0,
                                      1,
                                     );
   return $err_or_som
     unless ref($err_or_som);
+  warn "$me: set element configset (performance profile)\n" if $DEBUG;
 
   $err_or_som = $self->prizm_command('NetworkIfService',
                                      'activateNetworkElements',
                                      [ $element ],
                                      1,
-                                     1,
+                                     ( $self->option('ems') ? 1 : 0 ),
                                     );
 
   return $err_or_som
     unless ref($err_or_som);
+  warn "$me: activated element\n" if $DEBUG;
 
   $err_or_som = $self->prizm_command('CustomerIfService',
                                      'addElementToCustomer',
@@ -194,6 +321,7 @@ sub _export_insert {
 
   return $err_or_som
     unless ref($err_or_som);
+  warn "$me: added element to customer\n" if $DEBUG;
 
   '';
 }
@@ -201,39 +329,47 @@ sub _export_insert {
 sub _export_delete {
   my( $self, $svc ) = ( shift, shift );
 
-  my $custnum = $svc->cust_svc->cust_pkg->cust_main->custnum;
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
 
-  my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
-                                        ['MAC Address'],
-                                        [$svc->mac_addr],
-                                        ['='],
-                                       );
-  return $err_or_som
-    unless ref($err_or_som);
+  my $cust_pkg = $svc->cust_svc->cust_pkg;
 
-  return "Can't find prizm element for " . $svc->mac_addr
-    unless $err_or_som->result->[0];
+  my $depend = [];
 
-  $err_or_som = $self->prizm_command('NetworkIfService',
-                                     'suspendNetworkElements',
-                                     [$err_or_som->result->[0]->elementId],
-                                     1,
-                                     1,
-                                    );
+  if ($cust_pkg) {
+    my $queue = new FS::queue {
+      'svcnum' => $svc->svcnum,
+      'job'    => 'FS::part_export::prizm::queued_prizm_command',
+    };
+    my $error = $queue->insert(
+      ( map { $self->option($_) }
+            qw( url user password ) ),
+      'CustomerIfService',
+      'removeElementFromCustomer',
+      0,
+      $cust_pkg->custnum,
+      0,
+      $svc->mac_addr,
+    );
+
+    if ($error) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
 
-  return $err_or_som
-    unless ref($err_or_som);
+    push @$depend, $queue->jobnum;
+  }
 
-  $err_or_som = $self->prizm_command('CustomerIfService',
-                                     'removeElementFromCustomer',
-                                     0,
-                                     $custnum,
-                                     0,
-                                     $svc->mac_addr,
-                                    );
+  my $err_or_queue =
+    $self->queue_statuschange('deleteElement', $depend, $svc, 1);
 
-  return $err_or_som
-    unless ref($err_or_som);
+  unless (ref($err_or_queue)) {
+    $dbh->rollback if $oldAutoCommit;
+    return $err_or_queue;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
 
   '';
 }
@@ -266,7 +402,17 @@ sub _export_replace {
       grep { exists($freeside2prizm{$_}) }
         fields( 'svc_broadband' );
 
+  if ($old->description ne $new->description) {
+    my $cust_main = $old->cust_svc->cust_pkg->cust_main;
+    my $name = defined($cust_main->dbdef_table->column('ship_last'))
+             ? $cust_main->ship_name
+             : $cust_main->name;
+    push @values, $name . " " . $new->description;
+    push @names, "Site Name";
+  }
+
   my $element = $err_or_som->result->[0]->elementId;
+
   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
                                         [ $element ],
                                         \@names,
@@ -288,64 +434,159 @@ sub _export_replace {
   return $err_or_som
     unless ref($err_or_som);
 
+  my $performance_profile = $new->performance_profile;
+  $performance_profile ||= $new->cust_svc->cust_pkg->part_pkg->pkg;
+
+  $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
+                                     [ $element ],
+                                     $performance_profile,
+                                     0,
+                                     1,
+                                    );
+  return $err_or_som
+    unless ref($err_or_som);
+
   '';
 
 }
 
 sub _export_suspend {
   my( $self, $svc ) = ( shift, shift );
+  my $depend = [];
+  my $ems = $self->option('ems') ? 1 : 0;
+  my $err_or_queue = '';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  $err_or_queue = 
+     $self->queue_statuschange('suspendNetworkElements', [], $svc, 1, $ems);
+  unless (ref($err_or_queue)) {
+    $dbh->rollback if $oldAutoCommit;
+    return $err_or_queue;
+  }
+  push @$depend, $err_or_queue->jobnum;
+
+  if ($ems && $self->option('always_bam')) {
+    $err_or_queue =
+      $self->queue_statuschange('suspendNetworkElements', $depend, $svc, 1, 0);
+    unless (ref($err_or_queue)) {
+      $dbh->rollback if $oldAutoCommit;
+      return $err_or_queue;
+    }
+  }
 
-  my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
-                                        [ 'MAC Address' ],
-                                        [ $svc->mac_addr ],
-                                        [ '=' ],
-                                       );
-  return $err_or_som
-    unless ref($err_or_som);
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
 
-  return "Can't find prizm element for " . $svc->mac_addr
-    unless $err_or_som->result->[0];
+  '';
+}
 
-  $err_or_som = $self->prizm_command('NetworkIfService',
-                                     'suspendNetworkElements',
-                                     [ $err_or_som->result->[0]->elementId ],
-                                     1,
-                                     1,
-                                    );
+sub _export_unsuspend {
+  my( $self, $svc ) = ( shift, shift );
+  my $depend = [];
+  my $ems = $self->option('ems') ? 1 : 0;
+  my $err_or_queue = '';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  if ($ems && $self->option('always_bam')) {
+    $err_or_queue =
+      $self->queue_statuschange('activateNetworkElements', [], $svc, 1, 0);
+    unless (ref($err_or_queue)) {
+      $dbh->rollback if $oldAutoCommit;
+      return $err_or_queue;
+    }
+    push @$depend, $err_or_queue->jobnum;
+  }
 
-  return $err_or_som
-    unless ref($err_or_som);
+  $err_or_queue =
+    $self->queue_statuschange('activateNetworkElements', $depend, $svc, 1, $ems);
+  unless (ref($err_or_queue)) {
+    $dbh->rollback if $oldAutoCommit;
+    return $err_or_queue;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
 
   '';
+}
 
+sub export_links {
+  my( $self, $svc, $arrayref ) = ( shift, shift, shift );
+
+  push @$arrayref,
+    '<A HREF="http://'. $svc->ip_addr. '" target="_blank">SM</A>';
+
+  '';
 }
 
-sub _export_unsuspend {
-  my( $self, $svc ) = ( shift, shift );
+sub queue_statuschange {
+  my( $self, $method, $jobs, $svc, @args ) = @_;
+
+  # already in a transaction and can't die here
+
+  my $queue = new FS::queue {
+    'svcnum' => $svc->svcnum,
+    'job'    => 'FS::part_export::prizm::statuschange',
+  };
+  my $error = $queue->insert(
+    ( map { $self->option($_) }
+          qw( url user password ) ),
+    $method,
+    $svc->mac_addr,
+    @args,
+  );
 
-  my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
-                                        [ 'MAC Address' ],
-                                        [ $svc->mac_addr ],
-                                        [ '=' ],
-                                       );
-  return $err_or_som
+  unless ($error) {                   # successful insertion
+    foreach my $job ( @$jobs ) {
+      $error ||= $queue->depend_insert($job);
+    }
+  }
+
+  $error or $queue;
+}
+
+sub statuschange {  # subroutine
+  my( $url, $user, $password, $method, $mac_addr, @args) = @_;
+
+  eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
+  die $@ if $@;
+
+  my $prizm = new Net::Prizm (
+    namespace => 'NetworkIfService',
+    url => $url,
+    user => $user,
+    password => $password,
+  );
+  
+  my $err_or_som = $prizm->getPrizmElements( [ 'MAC Address' ],
+                                             [ $mac_addr ],
+                                             [ '=' ],
+                                           );
+  die $err_or_som
     unless ref($err_or_som);
 
-  return "Can't find prizm element for " . $svc->mac_addr
+  die "Can't find prizm element for " . $mac_addr
     unless $err_or_som->result->[0];
 
-  $err_or_som = $self->prizm_command('NetworkIfService',
-                                     'activateNetworkElements',
-                                     [ $err_or_som->result->[0]->elementId ],
-                                     1,
-                                     1,
-                                    );
+  my $arg1;
+  # yuck!
+  if ($method =~ /suspendNetworkElements/ || $method =~ /activateNetworkElements/) {
+    $arg1 = [ $err_or_som->result->[0]->elementId ];
+  }else{
+    $arg1 = $err_or_som->result->[0]->elementId;
+  }
+  $err_or_som = $prizm->$method( $arg1, @args );
 
-  return $err_or_som
+  die $err_or_som
     unless ref($err_or_som);
 
   '';
 
 }
 
+
 1;