1 package FS::part_export::prizm;
3 use vars qw(@ISA %info %options $DEBUG $me);
5 use FS::Record qw(fields dbh);
8 @ISA = qw(FS::part_export);
10 $me = '[' . __PACKAGE__ . ']';
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)' },
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>
27 Freeside will attempt to create an element in an existing network with the
28 values provided in svc_broadband. Of particular interest are
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
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.
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.
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
55 <li> workPhone - the daytime phone number
56 <li> homePhone - the night phone number
57 <li> freesideId - the freeside customer number
60 Additionally set on the element are
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
67 Freeside provisions, suspends, and unsuspends elements BAM only unless the
68 'Full EMS' checkbox is checked.<br><br>
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
82 'svc' => 'svc_broadband',
83 'desc' => 'Real-time export to Northbound Interface',
84 'options' => \%options,
91 my ($self,$namespace,$method) = (shift,shift,shift);
93 eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
96 my $prizm = new Net::Prizm (
97 namespace => $namespace,
98 url => $self->option('url'),
99 user => $self->option('user'),
100 password => $self->option('password'),
106 sub queued_prizm_command { # subroutine
107 my( $url, $user, $password, $namespace, $method, @args ) = @_;
109 eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
112 my $prizm = new Net::Prizm (
113 namespace => $namespace,
116 password => $password,
119 $err_or_som = $prizm->$method( @args);
122 unless ref($err_or_som);
129 my( $self, $svc ) = ( shift, shift );
130 warn "$me: _export_insert called for export ". $self->exportnum.
131 " on service ". $svc->svcnum. "\n"
134 my $cust_main = $svc->cust_svc->cust_pkg->cust_main;
136 my $err_or_som = $self->prizm_command('CustomerIfService', 'getCustomers',
138 [$cust_main->custnum],
142 unless ref($err_or_som);
145 if ( defined $cust_main->dbdef_table->column('ship_last') ) {
146 $pre = $cust_main->ship_last ? 'ship_' : '';
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)
152 my $contact = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
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;
161 my $chashref = $cust_main->hashref;
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',
176 extraFieldValues => [ $chashref->{"${pre}country"}, $cust_main->custnum,
180 $err_or_som = $self->prizm_command('CustomerIfService', 'addCustomer',
183 unless ref($err_or_som);
185 $pcustomer = $err_or_som->result;
186 warn "$me: added customer $pcustomer to prizm\n" if $DEBUG;
188 warn "multiple prizm customers found for $cust_main->custnum"
189 if scalar(@$pcustomer) > 1;
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],
198 # unless ref($err_or_som);
200 # return "No elements in network" unless exists $err_or_som->result->[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];
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?
213 $self->prizm_command( 'CustomerIfService', 'removeElementFromCustomer',
220 $err_or_som = $self->prizm_command( 'NetworkIfService', 'getPrizmElements',
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],
233 my $performance_profile = $svc->performance_profile;
234 $performance_profile ||= $svc->cust_svc->cust_pkg->part_pkg->pkg;
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',
242 substr($name . " " . $svc->description,
243 0, $element_name_length),
246 sprintf("%032X", $svc->authkey || 0),
247 $performance_profile,
249 ($self->option('ems') ? 1 : 0 ),
252 unless ref($err_or_som);
253 warn "$me: added provisioned element to prizm\n" if $DEBUG;
255 my (@names) = ('Management IP',
263 my (@values) = ($svc->ip_addr,
267 $name . " " . $svc->description,
271 $element = $err_or_som->result->elementId;
272 $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
280 unless ref($err_or_som);
281 warn "$me: set element configuration\n" if $DEBUG;
283 $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
290 unless ref($err_or_som);
291 warn "$me: set element vlan profile\n" if $DEBUG;
293 $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
295 $performance_profile,
300 unless ref($err_or_som);
301 warn "$me: set element configset (performance profile)\n" if $DEBUG;
303 $err_or_som = $self->prizm_command('NetworkIfService',
304 'activateNetworkElements',
307 ( $self->option('ems') ? 1 : 0 ),
311 unless ref($err_or_som);
312 warn "$me: activated element\n" if $DEBUG;
314 $err_or_som = $self->prizm_command('CustomerIfService',
315 'addElementToCustomer',
323 unless ref($err_or_som);
324 warn "$me: added element to customer\n" if $DEBUG;
330 my( $self, $svc ) = ( shift, shift );
332 my $oldAutoCommit = $FS::UID::AutoCommit;
333 local $FS::UID::AutoCommit = 0;
336 my $cust_pkg = $svc->cust_svc->cust_pkg;
341 my $queue = new FS::queue {
342 'svcnum' => $svc->svcnum,
343 'job' => 'FS::part_export::prizm::queued_prizm_command',
345 my $error = $queue->insert(
346 ( map { $self->option($_) }
347 qw( url user password ) ),
349 'removeElementFromCustomer',
357 $dbh->rollback if $oldAutoCommit;
361 push @$depend, $queue->jobnum;
365 $self->queue_statuschange('deleteElement', $depend, $svc, 1);
367 unless (ref($err_or_queue)) {
368 $dbh->rollback if $oldAutoCommit;
369 return $err_or_queue;
372 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
377 sub _export_replace {
378 my( $self, $new, $old ) = ( shift, shift, shift );
380 my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
386 unless ref($err_or_som);
388 return "Can't find prizm element for " . $old->mac_addr
389 unless $err_or_som->result->[0];
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',
400 my (@names) = map { push @values, $new->$_; $freeside2prizm{$_} }
401 grep { $old->$_ ne $new->$_ }
402 grep { exists($freeside2prizm{$_}) }
403 fields( 'svc_broadband' );
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
410 push @values, $name . " " . $new->description;
411 push @names, "Site Name";
414 my $element = $err_or_som->result->[0]->elementId;
416 $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
424 unless ref($err_or_som);
426 $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
432 if $old->vlan_profile ne $new->vlan_profile;
435 unless ref($err_or_som);
437 my $performance_profile = $new->performance_profile;
438 $performance_profile ||= $new->cust_svc->cust_pkg->part_pkg->pkg;
440 $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
442 $performance_profile,
447 unless ref($err_or_som);
453 sub _export_suspend {
454 my( $self, $svc ) = ( shift, shift );
456 my $ems = $self->option('ems') ? 1 : 0;
457 my $err_or_queue = '';
459 my $oldAutoCommit = $FS::UID::AutoCommit;
460 local $FS::UID::AutoCommit = 0;
464 $self->queue_statuschange('suspendNetworkElements', [], $svc, 1, $ems);
465 unless (ref($err_or_queue)) {
466 $dbh->rollback if $oldAutoCommit;
467 return $err_or_queue;
469 push @$depend, $err_or_queue->jobnum;
471 if ($ems && $self->option('always_bam')) {
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;
480 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
485 sub _export_unsuspend {
486 my( $self, $svc ) = ( shift, shift );
488 my $ems = $self->option('ems') ? 1 : 0;
489 my $err_or_queue = '';
491 my $oldAutoCommit = $FS::UID::AutoCommit;
492 local $FS::UID::AutoCommit = 0;
495 if ($ems && $self->option('always_bam')) {
497 $self->queue_statuschange('activateNetworkElements', [], $svc, 1, 0);
498 unless (ref($err_or_queue)) {
499 $dbh->rollback if $oldAutoCommit;
500 return $err_or_queue;
502 push @$depend, $err_or_queue->jobnum;
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;
512 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
518 my( $self, $svc, $arrayref ) = ( shift, shift, shift );
521 '<A HREF="http://'. $svc->ip_addr. '" target="_blank">SM</A>';
526 sub queue_statuschange {
527 my( $self, $method, $jobs, $svc, @args ) = @_;
529 # already in a transaction and can't die here
531 my $queue = new FS::queue {
532 'svcnum' => $svc->svcnum,
533 'job' => 'FS::part_export::prizm::statuschange',
535 my $error = $queue->insert(
536 ( map { $self->option($_) }
537 qw( url user password ) ),
543 unless ($error) { # successful insertion
544 foreach my $job ( @$jobs ) {
545 $error ||= $queue->depend_insert($job);
552 sub statuschange { # subroutine
553 my( $url, $user, $password, $method, $mac_addr, @args) = @_;
555 eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
558 my $prizm = new Net::Prizm (
559 namespace => 'NetworkIfService',
562 password => $password,
565 my $err_or_som = $prizm->getPrizmElements( [ 'MAC Address' ],
570 unless ref($err_or_som);
572 die "Can't find prizm element for " . $mac_addr
573 unless $err_or_som->result->[0];
577 if ($method =~ /suspendNetworkElements/ || $method =~ /activateNetworkElements/) {
578 $arg1 = [ $err_or_som->result->[0]->elementId ];
580 $arg1 = $err_or_som->result->[0]->elementId;
582 $err_or_som = $prizm->$method( $arg1, @args );
585 unless ref($err_or_som);