1 package FS::part_export::saisei;
4 use vars qw( @ISA %info );
5 use base qw( FS::part_export );
6 use Date::Format 'time2str';
18 FS::part_export::saisei
22 Saisei integration for Freeside
26 This export offers basic svc_broadband provisioning for Saisei.
28 This is a customer integration with Saisei. This will set up a rate plan and tie
29 the rate plan to a host and the access point via the Saisei API when the broadband service is provisioned.
30 It will also untie the host from the rate plan, setting it to the default rate plan via the API upon unprovisioning of the broadband service.
32 This will create and modify the rate plans at Saisei as soon as the broadband service attached to this export is created or modified.
33 This will also create and modify an access point at Saisei as soon as the tower is created or modified.
35 To use this export, follow the below instructions:
37 Create a new service definition and set the table to svc_broadband. The service name will become the Saisei rate plan name.
38 Set the upload and download speed for the service. This is required to be able to export the service to Saisei.
39 Attach this Saisei export to this service.
41 Create a tower and add a sector to that tower. The sector name will be the name of the access point,
42 Make sure you have set the up and down rate limit for the tower and the sector. This is required to be able to export the access point.
43 The tower and sector will be set up as access points at Saisei upon the creation of the tower or sector. They will be modified at Saisei when modified in freeside.
44 Each sector will be attached to its tower access point using the Saisei uplink field.
46 Create a package for the above created service, and order this package for a customer.
48 Provision the service, making sure to enter the IP address associated with this service and select the tower and sector for it's access point.
49 This provisioned service will then be exported as a host to Saisei.
51 Unprovisioning this service will set the host entry at Saisei to the default rate plan with the user and access point set to <none>.
53 After this export is set up and attached to a service, you can export the already provisioned services by clicking the link Export provisioned services attached to this export.
54 Clicking on this link will export all services attached to this export not currently exported to Saisei.
56 This module also provides generic methods for working through the L</Saisei API>.
60 tie my %scripts, 'Tie::IxHash',
61 'export_provisioned_services' => { component => '/elements/popup_link.html',
62 label => 'Export provisioned services',
63 description => 'will export provisioned services of part service with Saisei export attached.',
64 html_label => '<b>Export provisioned services attached to this export.</b>',
68 tie my %options, 'Tie::IxHash',
69 'port' => { label => 'Port',
71 'username' => { label => 'Saisei API User Name',
73 'password' => { label => 'Saisei API Password',
75 'debug' => { type => 'checkbox',
76 label => 'Enable debug warnings' },
80 'svc' => 'svc_broadband',
81 'desc' => 'Export broadband service/account to Saisei',
82 'options' => \%options,
83 'scripts' => \%scripts,
85 This is a customer integration with Saisei. This will set up a rate plan and tie
86 the rate plan to a host and the access point via the Saisei API when the broadband service is provisioned.
87 It will also untie the host from the rate plan, setting it to the default rate plan via the API upon unprovisioning of the broadband service.
89 This will create and modify the rate plans at Saisei as soon as the broadband service attached to this export is created or modified.
90 This will also create and modify an access point at Saisei as soon as the tower is created or modified.
92 To use this export, follow the below instructions:
96 Create a new service definition and set the table to svc_broadband. The service name will become the Saisei rate plan name.
97 Set the upload and download speed for the service. This is required to be able to export the service to Saisei.
98 Attach this Saisei export to this service.
102 Create a tower and add a sector to that tower. The sector name will be the name of the access point,
103 Make sure you have set the up and down rate limit for the tower and the sector. This is required to be able to export the access point.
104 The tower and sector will be set up as access points at Saisei upon the creation of the tower or sector. They will be modified at Saisei when modified in freeside.
105 Each sector will be attached to its tower access point using the Saisei uplink field.
109 Create a package for the above created service, and order this package for a customer.
113 Provision the service, making sure to enter the IP address associated with this service and select the tower and sector for it's access point.
114 This provisioned service will then be exported as a host to Saisei.
116 Unprovisioning this service will set the host entry at Saisei to the default rate plan with the user and access point set to <i>none</i>.
120 After this export is set up and attached to a service, you can export the already provisioned services by clicking the link <b>Export provisioned services attached to this export</b>.
121 Clicking on this link will export all services attached to this export not currently exported to Saisei.
130 my ($self, $svc_broadband) = @_;
132 my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } );
133 my $rateplan_name = $service_part->{Hash}->{svc};
134 $rateplan_name =~ s/\s/_/g;
136 # check for existing rate plan
137 my $existing_rateplan;
138 $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
140 # if no existing rate plan create one and modify it.
141 $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
142 $self->api_modify_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
143 return $self->api_error if $self->{'__saisei_error'};
145 # set rateplan to existing one or newly created one.
146 my $rateplan = $existing_rateplan ? $existing_rateplan : $self->api_get_rateplan($rateplan_name);
148 my $username = $svc_broadband->{Hash}->{svcnum};
149 my $description = $svc_broadband->{Hash}->{description};
152 $self->{'__saisei_error'} = 'no username - can not export';
153 return $self->api_error;
156 # check for existing user.
158 $existing_user = $self->api_get_user($username) unless $self->{'__saisei_error'};
160 # if no existing user create one.
161 $self->api_create_user($username, $description) unless $existing_user;
162 return $self->api_error if $self->{'__saisei_error'};
164 # set user to existing one or newly created one.
165 my $user = $existing_user ? $existing_user : $self->api_get_user($username);
168 my $tower_sector = FS::Record::qsearchs({
169 'table' => 'tower_sector',
170 'select' => 'tower.towername,
171 tower.up_rate_limit as tower_upratelimit,
172 tower.down_rate_limit as tower_downratelimit,
173 tower_sector.sectorname,
174 tower_sector.up_rate_limit as sector_upratelimit,
175 tower_sector.down_rate_limit as sector_downratelimit ',
176 'addl_from' => 'LEFT JOIN tower USING ( towernum )',
178 'sectornum' => $svc_broadband->{Hash}->{sectornum},
182 my $tower_name = $tower_sector->{Hash}->{towername};
183 $tower_name =~ s/\s/_/g;
186 'tower_name' => $tower_name,
187 'tower_uprate_limit' => $tower_sector->{Hash}->{tower_upratelimit},
188 'tower_downrate_limit' => $tower_sector->{Hash}->{tower_downratelimit},
191 my $tower_ap = process_tower($self, $tower_opt);
192 return $self->api_error if $self->{'__saisei_error'};
194 my $sector_name = $tower_sector->{Hash}->{sectorname};
195 $sector_name =~ s/\s/_/g;
198 'tower_name' => $tower_name,
199 'sector_name' => $sector_name,
200 'sector_uprate_limit' => $tower_sector->{Hash}->{sector_upratelimit},
201 'sector_downrate_limit' => $tower_sector->{Hash}->{sector_downratelimit},
203 my $accesspoint = process_sector($self, $sector_opt);
204 return $self->api_error if $self->{'__saisei_error'};
206 ## tie host to user add sector name as access point.
207 $self->api_add_host_to_user(
208 $user->{collection}->[0]->{name},
209 $rateplan->{collection}->[0]->{name},
210 $svc_broadband->{Hash}->{ip_addr},
211 $accesspoint->{collection}->[0]->{name},
212 ) unless $self->{'__saisei_error'};
215 return $self->api_error;
219 sub _export_replace {
220 my ($self, $svc_broadband) = @_;
225 my ($self, $svc_broadband) = @_;
227 my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } );
228 my $rateplan_name = $service_part->{Hash}->{svc};
229 $rateplan_name =~ s/\s/_/g;
230 my $username = $svc_broadband->{Hash}->{svcnum};
232 ## untie host to user
233 $self->api_delete_host_to_user($username, $rateplan_name, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
238 sub _export_suspend {
239 my ($self, $svc_broadband) = @_;
243 sub _export_unsuspend {
244 my ($self, $svc_broadband) = @_;
249 my ($self, $svc_part) = @_;
251 my $rateplan_name = $svc_part->{Hash}->{svc};
252 $rateplan_name =~ s/\s/_/g;
253 my $speeddown = $svc_part->{Hash}->{svc_broadband__speed_down};
254 my $speedup = $svc_part->{Hash}->{svc_broadband__speed_up};
256 my $temp_svc = $svc_part->{Hash};
257 my $svc_broadband = {};
258 map { if ($_ =~ /^svc_broadband__(.*)$/) { $svc_broadband->{Hash}->{$1} = $temp_svc->{$_}; } } keys %$temp_svc;
260 # check for existing rate plan
261 my $existing_rateplan;
262 $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
264 # Modify the existing rate plan with new service data.
265 $self->api_modify_existing_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || !$existing_rateplan);
267 # if no existing rate plan create one and modify it.
268 $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
269 $self->api_modify_rateplan($svc_part, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
271 return $self->api_error;
275 sub export_tower_sector {
276 my ($self, $tower) = @_;
278 #modify tower or create it.
279 my $tower_name = $tower->{Hash}->{towername};
280 $tower_name =~ s/\s/_/g;
282 'tower_name' => $tower_name,
283 'tower_uprate_limit' => $tower->{Hash}->{up_rate_limit},
284 'tower_downrate_limit' => $tower->{Hash}->{down_rate_limit},
285 'modify_existing' => '1', # modify an existing access point with this info
288 my $tower_access_point = process_tower($self, $tower_opt);
290 #get list of all access points
292 'table' => 'tower_sector',
294 'hashref' => { 'towernum' => $tower->{Hash}->{towernum}, },
297 #for each one modify or create it.
298 foreach my $tower_sector ( FS::Record::qsearch($hash_opt) ) {
299 my $sector_name = $tower_sector->{Hash}->{sectorname};
300 $sector_name =~ s/\s/_/g;
302 'tower_name' => $tower_name,
303 'sector_name' => $sector_name,
304 'sector_uprate_limit' => $tower_sector->{Hash}->{up_rate_limit},
305 'sector_downrate_limit' => $tower_sector->{Hash}->{down_rate_limit},
306 'modify_existing' => '1', # modify an existing access point with this info
308 my $sector_access_point = process_sector($self, $sector_opt);
311 return $self->api_error;
316 These methods allow access to the Saisei API using the credentials
317 set in the export options.
323 Accepts I<$method>, I<$path>, I<$params> hashref and optional.
324 Places an api call to the specified path and method with the specified params.
325 Returns the decoded json object returned by the api call.
326 Returns empty on failure; retrieve error messages using L</api_error>.
331 my ($self,$method,$path,$params) = @_;
333 $self->{'__saisei_error'} = '';
334 my $auth_info = $self->option('username') . ':' . $self->option('password');
337 warn "Calling $method on http://"
338 .$self->{Hash}->{machine}.':'.$self->option('port')
339 ."/rest/stm/configurations/running/$path\n" if $self->option('debug');
341 my $data = encode_json($params) if keys %{ $params };
343 my $client = REST::Client->new();
344 $client->addHeader("Authorization", "Basic ".encode_base64($auth_info));
345 $client->setHost('http://'.$self->{Hash}->{machine}.':'.$self->option('port'));
346 $client->$method('/rest/stm/configurations/running'.$path, $data, { "Content-type" => 'application/json'});
348 warn "Response Code is ".$client->responseCode()."\n" if $self->option('debug');
352 if ($client->responseCode() eq '200' || $client->responseCode() eq '201') {
353 eval { $result = decode_json($client->responseContent()) };
355 $self->{'__saisei_error'} = "Error decoding json: $@";
360 $self->{'__saisei_error'} = "Bad response from server during $method: " . $client->responseContent()
361 unless ($method eq "GET");
362 warn "Response Content is\n".$client->responseContent."\n" if $self->option('debug');
372 Returns the error string set by L</Saisei API> methods,
373 or a blank string if most recent call produced no errors.
379 return $self->{'__saisei_error'} || '';
382 =head2 api_get_policies
384 Gets a list of global policies.
388 sub api_get_policies {
391 my $get_policies = $self->api_call("GET", '/policies/?token=1&order=name&start=0&limit=20&select=name%2Cpercent_rate%2Cassured%2C');
392 return if $self->api_error;
393 $self->{'__saisei_error'} = "Did not receive any global policies"
394 unless $get_policies;
396 return $get_policies->{collection};
399 =head2 api_get_rateplan
401 Gets rateplan info for specific rateplan.
405 sub api_get_rateplan {
407 my $rateplan = shift;
409 my $get_rateplan = $self->api_call("GET", "/rate_plans/$rateplan");
410 return if $self->api_error;
412 return $get_rateplan;
417 Gets user info for specific user.
425 my $get_user = $self->api_call("GET", "/users/$user");
426 return if $self->api_error;
431 =head2 api_get_accesspoint
433 Gets user info for specific access point.
437 sub api_get_accesspoint {
439 my $accesspoint = shift;
441 my $get_accesspoint = $self->api_call("GET", "/access_points/$accesspoint");
442 return if $self->api_error;
444 return $get_accesspoint;
449 Gets user info for specific host.
457 my $get_host = $self->api_call("GET", "/hosts/$ip");
459 return if $self->api_error;
464 =head2 api_create_rateplan
470 sub api_create_rateplan {
471 my ($self, $svc, $rateplan) = @_;
473 $self->{'__saisei_error'} = "No downrate listed for service $rateplan" if !$svc->{Hash}->{speed_down};
474 $self->{'__saisei_error'} = "No uprate listed for service $rateplan" if !$svc->{Hash}->{speed_up};
476 my $new_rateplan = $self->api_call(
478 "/rate_plans/$rateplan",
480 'downstream_rate' => $svc->{Hash}->{speed_down},
481 'upstream_rate' => $svc->{Hash}->{speed_up},
483 ) unless $self->{'__saisei_error'};
485 $self->{'__saisei_error'} = "Rate Plan not created"
486 unless ($new_rateplan || $self->{'__saisei_error'});
488 return $new_rateplan;
492 =head2 api_modify_rateplan
494 Modify a new rateplan.
498 sub api_modify_rateplan {
499 my ($self,$svc,$rateplan_name) = @_;
502 my $policies = $self->api_get_policies();
504 foreach my $policy (@$policies) {
505 my $policyname = $policy->{name};
506 my $rate_multiplier = '';
507 if ($policy->{background}) { $rate_multiplier = ".01"; }
508 my $modified_rateplan = $self->api_call(
510 "/rate_plans/$rateplan_name/partitions/$policyname",
512 'restricted' => $policy->{assured}, # policy_assured_flag
513 'rate_multiplier' => $rate_multiplier, # policy_background 0.1
514 'rate' => $policy->{percent_rate}, # policy_percent_rate
518 $self->{'__saisei_error'} = "Rate Plan not modified after create"
519 unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
527 =head2 api_modify_existing_rateplan
529 Modify a existing rateplan.
533 sub api_modify_existing_rateplan {
534 my ($self,$svc,$rateplan_name) = @_;
536 my $modified_rateplan = $self->api_call(
538 "/rate_plans/$rateplan_name",
540 'downstream_rate' => $svc->{Hash}->{speed_down},
541 'upstream_rate' => $svc->{Hash}->{speed_up},
545 $self->{'__saisei_error'} = "Rate Plan not modified"
546 unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
552 =head2 api_create_user
558 sub api_create_user {
559 my ($self,$user, $description) = @_;
561 my $new_user = $self->api_call(
565 'description' => $description,
569 $self->{'__saisei_error'} = "User not created"
570 unless ($new_user || $self->{'__saisei_error'}); # should never happen
576 =head2 api_create_accesspoint
578 Creates a access point.
582 sub api_create_accesspoint {
583 my ($self,$accesspoint, $upratelimit, $downratelimit) = @_;
585 # this has not been tested, but should work, if needed.
586 my $new_accesspoint = $self->api_call(
588 "/access_points/$accesspoint",
590 'downstream_rate_limit' => $downratelimit,
591 'upstream_rate_limit' => $upratelimit,
595 $self->{'__saisei_error'} = "Access point not created"
596 unless ($new_accesspoint || $self->{'__saisei_error'}); # should never happen
601 =head2 api_modify_accesspoint
603 Modify a new access point.
607 sub api_modify_accesspoint {
608 my ($self, $accesspoint, $uplink) = @_;
610 my $modified_accesspoint = $self->api_call(
612 "/access_points/$accesspoint",
614 'uplink' => $uplink, # name of attached access point
618 $self->{'__saisei_error'} = "Rate Plan not modified"
619 unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
625 =head2 api_modify_existing_accesspoint
627 Modify a existing accesspoint.
631 sub api_modify_existing_accesspoint {
632 my ($self, $accesspoint, $uplink, $upratelimit, $downratelimit) = @_;
634 my $modified_accesspoint = $self->api_call(
636 "/access_points/$accesspoint",
638 'downstream_rate_limit' => $downratelimit,
639 'upstream_rate_limit' => $upratelimit,
640 # 'uplink' => $uplink, # name of attached access point
644 $self->{'__saisei_error'} = "Access point not modified"
645 unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
651 =head2 api_add_host_to_user
653 ties host to user, rateplan and default access point.
657 sub api_add_host_to_user {
658 my ($self,$user, $rateplan, $ip, $accesspoint) = @_;
660 my $new_host = $self->api_call(
665 'rate_plan' => $rateplan,
666 'access_point' => $accesspoint,
670 $self->{'__saisei_error'} = "Host not created"
671 unless ($new_host || $self->{'__saisei_error'}); # should never happen
677 =head2 api_delete_host_to_user
679 unties host from user and rateplan.
680 this will set the host entry at Saisei to the default rate plan with the user and access point set to <none>.
684 sub api_delete_host_to_user {
685 my ($self,$user, $rateplan, $ip) = @_;
687 my $default_rate_plan = $self->api_call("GET", '?token=1&select=default_rate_plan');
688 return if $self->api_error;
689 $self->{'__saisei_error'} = "Did not receive a default rate plan"
690 unless $default_rate_plan;
692 my $default_rateplan_name = $default_rate_plan->{collection}->[0]->{default_rate_plan}->{link}->{name};
694 my $delete_host = $self->api_call(
699 'access_point' => '<none>',
700 'rate_plan' => $default_rateplan_name,
704 $self->{'__saisei_error'} = "Host not created"
705 unless ($delete_host || $self->{'__saisei_error'}); # should never happen
712 my ($self, $opt) = @_;
714 my $existing_tower_ap;
715 my $tower_name = $opt->{tower_name};
717 #check if tower has been set up as an access point.
718 $existing_tower_ap = $self->api_get_accesspoint($tower_name) unless $self->{'__saisei_error'};
720 # modify the existing accesspoint if changing tower .
721 $self->api_modify_existing_accesspoint (
723 '', # tower does not have a uplink on sectors.
724 $opt->{tower_uprate_limit},
725 $opt->{tower_downrate_limit},
726 ) if $existing_tower_ap && $opt->{modify_existing};
728 #if tower does not exist as an access point create it.
729 $self->api_create_accesspoint(
731 $opt->{tower_uprate_limit},
732 $opt->{tower_downrate_limit}
733 ) unless $existing_tower_ap;
735 my $accesspoint = $self->api_get_accesspoint($tower_name);
741 my ($self, $opt) = @_;
743 my $existing_sector_ap;
744 my $sector_name = $opt->{sector_name};
746 #check if sector has been set up as an access point.
747 $existing_sector_ap = $self->api_get_accesspoint($sector_name);
749 # modify the existing accesspoint if changing sector .
750 $self->api_modify_existing_accesspoint (
753 $opt->{sector_uprate_limit},
754 $opt->{sector_downrate_limit},
755 ) if $existing_sector_ap && $opt->{modify_existing};
757 #if sector does not exist as an access point create it.
758 $self->api_create_accesspoint(
760 $opt->{sector_uprate_limit},
761 $opt->{sector_downrate_limit},
762 ) unless $existing_sector_ap;
764 # Attach newly created sector to it's tower.
765 $self->api_modify_accesspoint($sector_name, $opt->{tower_name}) unless ($self->{'__saisei_error'} || $existing_sector_ap);
767 # set access point to existing one or newly created one.
768 my $accesspoint = $existing_sector_ap ? $existing_sector_ap : $self->api_get_accesspoint($sector_name);
773 sub export_provisioned_services {
775 my $param = thaw(decode_base64(shift));
777 my $part_export = FS::Record::qsearchs('part_export', { 'exportnum' => $param->{export_provisioned_services_exportnum}, } )
778 or die "unknown exportnum $param->{export_provisioned_services_exportnum}";
781 my @svcparts = FS::Record::qsearch({
782 'table' => 'export_svc',
783 'addl_from' => 'LEFT JOIN part_svc USING ( svcpart ) ',
784 'hashref' => { 'exportnum' => $param->{export_provisioned_services_exportnum}, },
786 my $part_count = scalar @svcparts;
788 my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
790 my @svcs = FS::Record::qsearch({
791 'table' => 'cust_svc',
792 'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum ) ',
793 'extra_sql' => " WHERE svcpart in ('".$parts."')",
796 my $svc_count = scalar @svcs;
799 for (my $c=10; $c <=100; $c=$c+10) { $status{int($svc_count * ($c/100))} = $c; }
802 foreach my $svc (@svcs) {
803 if ($status{$process_count}) { my $s = $status{$process_count}; $job->update_statustext($s); }
804 ## check if service exists as host if not export it.
805 _export_insert($part_export,$svc) unless api_get_host($part_export, $svc->{Hash}->{ip_addr});