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.
45 Each access point will be attached to the interface set in the export config. If left blank access point will be attached to the default interface. Most setups can leave this blank.
47 Create a package for the above created service, and order this package for a customer.
49 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.
50 This provisioned service will then be exported as a host to Saisei.
52 Unprovisioning this service will set the host entry at Saisei to the default rate plan with the user and access point set to <none>.
54 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.
55 Clicking on this link will export all services attached to this export not currently exported to Saisei.
57 This module also provides generic methods for working through the L</Saisei API>.
61 tie my %scripts, 'Tie::IxHash',
62 'export_provisioned_services' => { component => '/elements/popup_link.html',
63 label => 'Export provisioned services',
64 description => 'will export provisioned services of part service with Saisei export attached.',
65 html_label => '<b>Export provisioned services attached to this export.</b>',
66 error_url => '/edit/part_export.cgi?',
67 success_message => 'Saisei export of provisioned services successful',
69 'export_all_towers_sectors' => { component => '/elements/popup_link.html',
70 label => 'Export of all towers and sectors',
71 description => 'Will force an export of all towers and sectors to Saisei as access points.',
72 html_label => '<b>Export all towers and sectors.</b>',
73 error_url => '/edit/part_export.cgi?',
74 success_message => 'Saisei export of towers and sectors as access points successful',
78 tie my %options, 'Tie::IxHash',
79 'port' => { label => 'Port',
81 'username' => { label => 'Saisei API User Name',
83 'password' => { label => 'Saisei API Password',
85 'interface' => { label => 'Saisei Access Point Interface',
87 'debug' => { type => 'checkbox',
88 label => 'Enable debug warnings' },
92 'svc' => 'svc_broadband',
93 'desc' => 'Export broadband service/account to Saisei',
94 'options' => \%options,
95 'scripts' => \%scripts,
97 This is a customer integration with Saisei. This will set up a rate plan and tie
98 the rate plan to a host and the access point via the Saisei API when the broadband service is provisioned.
99 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.
101 This will create and modify the rate plans at Saisei as soon as the broadband service attached to this export is created or modified.
102 This will also create and modify an access point at Saisei as soon as the tower is created or modified.
104 To use this export, follow the below instructions:
108 Create a new service definition and set the table to svc_broadband. The service name will become the Saisei rate plan name.
109 Set the upload speed, download speed, and tower to be required for the service. This is required to be able to export the service to Saisei.
110 Attach this Saisei export to this service.
114 Create a tower and add a sector to that tower. The sector name will be the name of the access point,
115 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.
116 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.
117 Each sector will be attached to its tower access point using the Saisei uplink field.
118 Each access point will be attached to the interface set in the export config. If left blank access point will be attached to the default interface. Most setups can leave this blank.
122 Create a package for the above created service, and order this package for a customer.
126 Provision the service, making sure to enter the IP address associated with this service, the upload and download speed are correct, and select the tower and sector for it's access point.
127 This provisioned service will then be exported as a host to Saisei.
129 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>.
133 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>.
134 Clicking on this link will export all services attached to this export not currently exported to Saisei.
138 <A HREF="http://www.freeside.biz/mediawiki/index.php/Saisei_provisioning_export" target="_new">Documentation</a>
143 my ($self, $svc_broadband) = @_;
145 my $rateplan_name = $self->get_rateplan_name($svc_broadband);
147 # check for existing rate plan
148 my $existing_rateplan;
149 $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
151 # if no existing rate plan create one and modify it.
152 $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
153 $self->api_modify_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
154 return $self->api_error if $self->{'__saisei_error'};
156 # set rateplan to existing one or newly created one.
157 my $rateplan = $existing_rateplan ? $existing_rateplan : $self->api_get_rateplan($rateplan_name);
159 my $username = $svc_broadband->{Hash}->{svcnum};
160 my $description = $svc_broadband->{Hash}->{description};
162 $svc_location = $svc_broadband->{Hash}->{latitude}.','.$svc_broadband->{Hash}->{longitude} if ($svc_broadband->{Hash}->{latitude} && $svc_broadband->{Hash}->{longitude});
165 $self->{'__saisei_error'} = 'no username - can not export';
166 return $self->api_error;
169 # check for existing user.
171 $existing_user = $self->api_get_user($username) unless $self->{'__saisei_error'};
173 # if no existing user create one.
174 $self->api_create_user($username, $description, $svc_location) unless $existing_user;
175 return $self->api_error if $self->{'__saisei_error'};
177 # set user to existing one or newly created one.
178 my $user = $existing_user ? $existing_user : $self->api_get_user($username);
181 my $tower_sector = FS::Record::qsearchs({
182 'table' => 'tower_sector',
183 'select' => 'tower.towername,
184 tower.up_rate_limit as tower_upratelimit,
185 tower.down_rate_limit as tower_downratelimit,
186 tower_sector.sectorname,
187 tower_sector.towernum,
188 tower_sector.up_rate_limit as sector_upratelimit,
189 tower_sector.down_rate_limit as sector_downratelimit,
192 'addl_from' => 'LEFT JOIN tower USING ( towernum )',
194 'sectornum' => $svc_broadband->{Hash}->{sectornum},
199 $tower_location = $tower_sector->{Hash}->{latitude}.','.$tower_sector->{Hash}->{longitude} if ($tower_sector->{Hash}->{latitude} && $tower_sector->{Hash}->{longitude});
201 my $tower_name = $tower_sector->{Hash}->{towername};
202 $tower_name =~ s/\s/_/g;
205 'tower_name' => $tower_name,
206 'tower_num' => $tower_sector->{Hash}->{towernum},
207 'tower_uprate_limit' => $tower_sector->{Hash}->{tower_upratelimit},
208 'tower_downrate_limit' => $tower_sector->{Hash}->{tower_downratelimit},
210 $tower_opt->{'location'} = $tower_location if $tower_location;
212 my $tower_ap = process_tower($self, $tower_opt);
213 return $self->api_error if $self->{'__saisei_error'};
215 my $sector_name = $tower_sector->{Hash}->{sectorname};
216 $sector_name =~ s/\s/_/g;
219 'tower_name' => $tower_name,
220 'tower_num' => $tower_sector->{Hash}->{towernum},
221 'sector_name' => $sector_name,
222 'sector_uprate_limit' => $tower_sector->{Hash}->{sector_upratelimit},
223 'sector_downrate_limit' => $tower_sector->{Hash}->{sector_downratelimit},
224 'rateplan' => $rateplan_name,
226 $sector_opt->{'location'} = $tower_location if $tower_location;
228 my $accesspoint = process_sector($self, $sector_opt);
229 return $self->api_error if $self->{'__saisei_error'};
231 ## get custnum and pkgpart from cust_pkg for virtual access point
232 my $cust_pkg = FS::Record::qsearchs({
233 'table' => 'cust_pkg',
234 'hashref' => { 'pkgnum' => $svc_broadband->{Hash}->{pkgnum}, },
236 my $virtual_ap_name = $cust_pkg->{Hash}->{custnum}.'_'.$cust_pkg->{Hash}->{pkgpart}.'_'.$svc_broadband->{Hash}->{speed_down}.'_'.$svc_broadband->{Hash}->{speed_up};
238 my $virtual_ap_opt = {
239 'virtual_name' => $virtual_ap_name,
240 'sector_name' => $sector_name,
241 'virtual_uprate_limit' => $svc_broadband->{Hash}->{speed_up},
242 'virtual_downrate_limit' => $svc_broadband->{Hash}->{speed_down},
244 my $virtual_ap = process_virtual_ap($self, $virtual_ap_opt);
245 return $self->api_error if $self->{'__saisei_error'};
247 ## tie host to user add sector name as access point.
249 'user' => $user->{collection}->[0]->{name},
250 'rateplan' => $rateplan->{collection}->[0]->{name},
251 'ip' => $svc_broadband->{Hash}->{ip_addr},
252 'accesspoint' => $virtual_ap->{collection}->[0]->{name},
253 'location' => $svc_location,
255 $self->api_add_host_to_user($host_opt)
256 unless $self->{'__saisei_error'};
259 return $self->api_error;
263 sub _export_replace {
264 my ($self, $svc_broadband) = @_;
265 my $error = $self->_export_insert($svc_broadband);
270 my ($self, $svc_broadband) = @_;
272 my $rateplan_name = $self->get_rateplan_name($svc_broadband);
274 my $username = $svc_broadband->{Hash}->{svcnum};
276 ## untie host to user
277 $self->api_delete_host_to_user($username, $rateplan_name, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
282 sub _export_suspend {
283 my ($self, $svc_broadband) = @_;
287 sub _export_unsuspend {
288 my ($self, $svc_broadband) = @_;
293 my ($self, $svc_part) = @_;
296 if ($svc_part->{Hash}->{svc_broadband__speed_down} eq "down" || $svc_part->{Hash}->{svc_broadband__speed_up} eq "up") {
297 for my $type (qw( down up )) {
298 my $speed_type = "broadband_".$type."stream";
299 foreach my $pkg_svc (FS::Record::qsearch({
300 'table' => 'pkg_svc',
301 'select' => 'pkg_svc.*, part_pkg_fcc_option.fccoptionname, part_pkg_fcc_option.optionvalue',
302 'addl_from' => ' LEFT JOIN part_pkg_fcc_option USING (pkgpart) ',
303 'extra_sql' => " WHERE pkg_svc.svcpart = ".$svc_part->{Hash}->{svcpart}." AND pkg_svc.quantity > 0 AND part_pkg_fcc_option.fccoptionname = '".$speed_type."'",
304 })) { $fcc_477_speeds->{
305 $pkg_svc->{Hash}->{pkgpart}}->{$speed_type} = $pkg_svc->{Hash}->{optionvalue} * 1000 unless !$pkg_svc->{Hash}->{optionvalue}; }
309 $fcc_477_speeds->{1}->{broadband_downstream} = $svc_part->{Hash}->{"svc_broadband__speed_down"};
310 $fcc_477_speeds->{1}->{broadband_upstream} = $svc_part->{Hash}->{"svc_broadband__speed_up"};
313 foreach my $key (keys %$fcc_477_speeds) {
315 $svc_part->{Hash}->{speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream};
316 $svc_part->{Hash}->{speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream};
317 $svc_part->{Hash}->{svc_broadband__speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream};
318 $svc_part->{Hash}->{svc_broadband__speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream};
320 my $temp_svc = $svc_part->{Hash};
321 my $svc_broadband = {};
322 map { if ($_ =~ /^svc_broadband__(.*)$/) { $svc_broadband->{Hash}->{$1} = $temp_svc->{$_}; } } keys %$temp_svc;
324 my $rateplan_name = $self->get_rateplan_name($svc_broadband, $svc_part->{Hash}->{svc});
326 # check for existing rate plan
327 my $existing_rateplan;
328 $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
330 # Modify the existing rate plan with new service data.
331 $self->api_modify_existing_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || !$existing_rateplan);
333 # if no existing rate plan create one and modify it.
334 $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
335 $self->api_modify_rateplan($svc_part, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
339 return $self->api_error;
343 sub export_tower_sector {
344 my ($self, $tower) = @_;
347 $tower_location = $tower->{Hash}->{latitude}.','.$tower->{Hash}->{longitude} if ($tower->{Hash}->{latitude} && $tower->{Hash}->{longitude});
349 #modify tower or create it.
350 my $tower_name = $tower->{Hash}->{towername};
351 $tower_name =~ s/\s/_/g;
354 'tower_name' => $tower_name,
355 'tower_num' => $tower->{Hash}->{towernum},
356 'tower_uprate_limit' => $tower->{Hash}->{up_rate_limit},
357 'tower_downrate_limit' => $tower->{Hash}->{down_rate_limit},
358 'modify_existing' => '1', # modify an existing access point with this info
360 $tower_opt->{'location'} = $tower_location if $tower_location;
362 my $tower_access_point = process_tower($self, $tower_opt);
363 return $tower_access_point if $tower_access_point->{error};
365 #get list of all access points
367 'table' => 'tower_sector',
369 'hashref' => { 'towernum' => $tower->{Hash}->{towernum}, },
372 #for each one modify or create it.
373 foreach my $tower_sector ( FS::Record::qsearch($hash_opt) ) {
374 next if $tower_sector->{Hash}->{sectorname} eq "_default";
375 my $sector_name = $tower_sector->{Hash}->{sectorname};
376 $sector_name =~ s/\s/_/g;
378 'tower_name' => $tower_name,
379 'tower_num' => $tower_sector->{Hash}->{towernum},
380 'sector_name' => $sector_name,
381 'sector_uprate_limit' => $tower_sector->{Hash}->{up_rate_limit},
382 'sector_downrate_limit' => $tower_sector->{Hash}->{down_rate_limit},
383 'modify_existing' => '1', # modify an existing access point with this info
385 $sector_opt->{'location'} = $tower_location if $tower_location;
387 my $sector_access_point = process_sector($self, $sector_opt) unless ($sector_name eq "_default");
388 return $sector_access_point if $sector_access_point->{error};
391 return { error => $self->api_error, };
394 ## creates the rateplan name
395 sub get_rateplan_name {
396 my ($self, $svc_broadband, $svc_name) = @_;
398 my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } ) unless $svc_name;
399 my $service_name = $svc_name ? $svc_name : $service_part->{Hash}->{svc};
401 my $rateplan_name = $service_name . " " . $svc_broadband->{Hash}->{speed_down} . "-" . $svc_broadband->{Hash}->{speed_up};
402 $rateplan_name =~ s/\s/_/g; $rateplan_name =~ s/[^A-Za-z0-9\-_]//g;
404 return $rateplan_name;
409 These methods allow access to the Saisei API using the credentials
410 set in the export options.
416 Accepts I<$method>, I<$path>, I<$params> hashref and optional.
417 Places an api call to the specified path and method with the specified params.
418 Returns the decoded json object returned by the api call.
419 Returns empty on failure; retrieve error messages using L</api_error>.
424 my ($self,$method,$path,$params) = @_;
426 $self->{'__saisei_error'} = '';
427 my $auth_info = $self->option('username') . ':' . $self->option('password');
430 warn "Calling $method on http://"
431 .$self->{Hash}->{machine}.':'.$self->option('port')
432 ."/rest/top/configurations/running/$path\n" if $self->option('debug');
434 my $data = encode_json($params) if keys %{ $params };
436 my $client = REST::Client->new();
437 $client->addHeader("Authorization", "Basic ".encode_base64($auth_info));
438 $client->setHost('http://'.$self->{Hash}->{machine}.':'.$self->option('port'));
439 $client->$method('/rest/top/configurations/running'.$path, $data, { "Content-type" => 'application/json'});
441 warn "Saisei Response Code is ".$client->responseCode()."\n" if $self->option('debug');
445 if ($client->responseCode() eq '200' || $client->responseCode() eq '201') {
446 eval { $result = decode_json($client->responseContent()) };
448 $self->{'__saisei_error'} = "There was an error decoding the JSON data from Saisei. Bad JSON data logged in error log if debug option was set.";
449 warn "Saisei RC 201 Response Content is not json\n".$client->responseContent()."\n" if $self->option('debug');
453 elsif ($client->responseCode() eq '404') {
454 eval { $result = decode_json($client->responseContent()) };
456 $self->{'__saisei_error'} = "There was an error decoding the JSON data from Saisei. Bad JSON data logged in error log if debug option was set.";
457 warn "Saisei RC 404 Response Content is not json\n".$client->responseContent()."\n" if $self->option('debug');
460 ## check if message is for empty hash.
461 my($does_not_exist) = $result->{message} =~ /'(.*)' does not exist$/;
462 $self->{'__saisei_error'} = "Saisei Error: ".$result->{message} unless $does_not_exist;
463 warn "Saisei Response Content is\n".$client->responseContent."\n" if ($self->option('debug') && !$does_not_exist);
466 elsif ($client->responseCode() eq '500') {
467 $self->{'__saisei_error'} = "Could not connect to the Saisei export host machine (".$self->{Hash}->{machine}.':'.$self->option('port').") during $method , we received the responce code: " . $client->responseCode();
468 warn "Saisei Response Content is\n".$client->responseContent."\n" if $self->option('debug');
472 $self->{'__saisei_error'} = "Received Bad response from server during $method $path $data, we received responce code: " . $client->responseCode() . " " . $client->responseContent;
473 warn "Saisei Response Content is\n".$client->responseContent."\n" if $self->option('debug');
483 Returns the error string set by L</Saisei API> methods,
484 or a blank string if most recent call produced no errors.
490 return $self->{'__saisei_error'} || '';
493 =head2 api_get_policies
495 Gets a list of global policies.
499 sub api_get_policies {
502 my $get_policies = $self->api_call("GET", '/policies/?token=1&order=name&start=0&limit=20&select=name%2Cpercent_rate%2Cassured%2C');
503 return if $self->api_error;
504 $self->{'__saisei_error'} = "Did not receive any global policies from Saisei."
505 unless $get_policies;
507 return $get_policies->{collection};
510 =head2 api_get_rateplan
512 Gets rateplan info for specific rateplan.
516 sub api_get_rateplan {
518 my $rateplan = shift;
520 my $get_rateplan = $self->api_call("GET", "/rate_plans/$rateplan");
521 return if $self->api_error;
523 return $get_rateplan;
528 Gets user info for specific user.
536 my $get_user = $self->api_call("GET", "/users/$user");
537 return if $self->api_error;
542 =head2 api_get_accesspoint
544 Gets user info for specific access point.
548 sub api_get_accesspoint {
550 my $accesspoint = shift;
552 my $get_accesspoint = $self->api_call("GET", "/access_points/$accesspoint");
553 return if $self->api_error;
555 return $get_accesspoint;
560 Gets user info for specific host.
568 my $get_host = $self->api_call("GET", "/hosts/$ip");
570 return { message => $self->api_error, } if $self->api_error;
575 =head2 api_create_rateplan
581 sub api_create_rateplan {
582 my ($self, $svc, $rateplan) = @_;
584 $self->{'__saisei_error'} = "There is no download speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a download speed set for them." if !$svc->{Hash}->{speed_down};
585 $self->{'__saisei_error'} = "There is no upload speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a upload speed set for them." if !$svc->{Hash}->{speed_up};
587 my $new_rateplan = $self->api_call(
589 "/rate_plans/$rateplan",
591 'downstream_rate' => $svc->{Hash}->{speed_down},
592 'upstream_rate' => $svc->{Hash}->{speed_up},
594 ) unless $self->{'__saisei_error'};
596 $self->{'__saisei_error'} = "Saisei could not create the rate plan $rateplan."
597 unless ($new_rateplan || $self->{'__saisei_error'});
599 return $new_rateplan;
603 =head2 api_modify_rateplan
605 Modify a new rateplan.
609 sub api_modify_rateplan {
610 my ($self,$svc,$rateplan_name) = @_;
613 my $policies = $self->api_get_policies();
615 foreach my $policy (@$policies) {
616 my $policyname = $policy->{name};
617 my $rate_multiplier = '';
618 if ($policy->{background}) { $rate_multiplier = ".01"; }
619 my $modified_rateplan = $self->api_call(
621 "/rate_plans/$rateplan_name/partitions/$policyname",
623 'restricted' => $policy->{assured}, # policy_assured_flag
624 'rate_multiplier' => $rate_multiplier, # policy_background 0.1
625 'rate' => $policy->{percent_rate}, # policy_percent_rate
629 $self->{'__saisei_error'} = "Saisei could not modify the rate plan $rateplan_name after it was created."
630 unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
638 =head2 api_modify_existing_rateplan
640 Modify a existing rateplan.
644 sub api_modify_existing_rateplan {
645 my ($self,$svc,$rateplan_name) = @_;
647 $self->{'__saisei_error'} = "There is no download speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan_name."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a download speed set for them." if !$svc->{Hash}->{speed_down};
648 $self->{'__saisei_error'} = "There is no upload speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan_name."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a upload speed set for them." if !$svc->{Hash}->{speed_up};
650 my $modified_rateplan = $self->api_call(
652 "/rate_plans/$rateplan_name",
654 'downstream_rate' => $svc->{Hash}->{speed_down},
655 'upstream_rate' => $svc->{Hash}->{speed_up},
659 $self->{'__saisei_error'} = "Saisei could not modify the rate plan $rateplan_name."
660 unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
666 =head2 api_create_user
672 sub api_create_user {
673 my ($self,$user, $description, $location) = @_;
676 'description' => $description,
678 $user_hash->{'map_location'} = $location if $location;
680 my $new_user = $self->api_call(
686 $self->{'__saisei_error'} = "Saisei could not create the user $user"
687 unless ($new_user || $self->{'__saisei_error'}); # should never happen
693 =head2 api_create_accesspoint
695 Creates a access point.
699 sub api_create_accesspoint {
700 my ($self,$accesspoint, $upratelimit, $downratelimit, $location) = @_;
703 'downstream_rate_limit' => $downratelimit,
704 'upstream_rate_limit' => $upratelimit,
705 'interface' => $self->option('interface'),
707 $ap_hash->{'map_location'} = $location if $location;
709 my $new_accesspoint = $self->api_call(
711 "/access_points/$accesspoint",
715 $self->{'__saisei_error'} = "Saisei could not create the access point $accesspoint"
716 unless ($new_accesspoint || $self->{'__saisei_error'}); # should never happen
721 =head2 api_modify_accesspoint
723 Modify a new access point.
727 sub api_modify_accesspoint {
728 my ($self, $accesspoint, $uplink, $location) = @_;
732 'interface' => $self->option('interface'),
734 $ap_hash->{'map_location'} = $location if $location;
736 my $modified_accesspoint = $self->api_call(
738 "/access_points/$accesspoint",
742 $self->{'__saisei_error'} = "Saisei could not modify the access point $accesspoint after it was created."
743 unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
749 =head2 api_modify_existing_accesspoint
751 Modify a existing accesspoint.
755 sub api_modify_existing_accesspoint {
756 my ($self, $accesspoint, $uplink, $upratelimit, $downratelimit, $location) = @_;
759 'downstream_rate_limit' => $downratelimit,
760 'upstream_rate_limit' => $upratelimit,
761 'interface' => $self->option('interface'),
762 # 'uplink' => $uplink, # name of attached access point
764 $ap_hash->{'map_location'} = $location if $location;
766 my $modified_accesspoint = $self->api_call(
768 "/access_points/$accesspoint",
772 $self->{'__saisei_error'} = "Saisei could not modify the access point $accesspoint."
773 unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
779 =head2 api_add_host_to_user
781 ties host to user, rateplan and default access point.
785 sub api_add_host_to_user {
786 # my ($self,$user, $rateplan, $ip, $accesspoint, $location) = @_;
787 my ($self,$opt) = @_;
788 my $ip = $opt->{'ip'};
789 my $location = $opt->{'location'};
792 'user' => $opt->{'user'},
793 'rate_plan' => $opt->{'rateplan'},
794 'access_point' => $opt->{'accesspoint'},
796 $newhost_hash->{'map_location'} = $location if $location;
798 my $new_host = $self->api_call(
804 $self->{'__saisei_error'} = "Saisei could not create the host $ip"
805 unless ($new_host || $self->{'__saisei_error'}); # should never happen
811 =head2 api_delete_host_to_user
813 unties host from user and rateplan.
814 this will set the host entry at Saisei to the default rate plan with the user and access point set to <none>.
818 sub api_delete_host_to_user {
819 my ($self,$user, $rateplan, $ip) = @_;
821 my $default_rate_plan = $self->api_call("GET", '?token=1&select=default_rate_plan');
822 return if $self->api_error;
823 $self->{'__saisei_error'} = "Can not delete the host as Saisei did not return a default rate plan. Please make sure Saisei has a default rateplan setup."
824 unless $default_rate_plan;
826 my $default_rateplan_name = $default_rate_plan->{collection}->[0]->{default_rate_plan}->{link}->{name};
828 my $delete_host = $self->api_call(
833 'access_point' => '<none>',
834 'rate_plan' => $default_rateplan_name,
838 $self->{'__saisei_error'} = "Saisei could not delete the host $ip"
839 unless ($delete_host || $self->{'__saisei_error'}); # should never happen
846 my ($self, $opt) = @_;
848 if (!$opt->{tower_uprate_limit} || !$opt->{tower_downrate_limit}) {
849 $self->{'__saisei_error'} = "Could not export tower !--tower,".$opt->{tower_num}.",".$opt->{tower_name}."--! because there was no up or down rates attached to the tower. Saisei requires a up and down rate be attached to each tower.";
850 return { error => $self->api_error, };
853 my $existing_tower_ap;
854 my $tower_name = $opt->{tower_name};
855 my $location = $opt->{location};
857 #check if tower has been set up as an access point.
858 $existing_tower_ap = $self->api_get_accesspoint($tower_name) unless $self->{'__saisei_error'};
860 # modify the existing accesspoint if changing tower .
861 $self->api_modify_existing_accesspoint (
863 '', # tower does not have a uplink on sectors.
864 $opt->{tower_uprate_limit},
865 $opt->{tower_downrate_limit},
867 ) if $existing_tower_ap->{collection} && $opt->{modify_existing};
869 #if tower does not exist as an access point create it.
870 $self->api_create_accesspoint(
872 $opt->{tower_uprate_limit},
873 $opt->{tower_downrate_limit},
875 ) unless $existing_tower_ap->{collection};
877 my $accesspoint = $self->api_get_accesspoint($tower_name);
879 return { error => $self->api_error, } if $self->api_error;
884 my ($self, $opt) = @_;
886 if (!$opt->{sector_name} || $opt->{sector_name} eq '_default') {
887 $self->{'__saisei_error'} = "No sector attached to Tower (".$opt->{tower_name}.") for service ".$opt->{'rateplan'}.". Saisei requires a tower sector to be attached to each service that is exported to Saisei.";
888 return { error => $self->api_error, };
891 if (!$opt->{sector_uprate_limit} || !$opt->{sector_downrate_limit}) {
892 $self->{'__saisei_error'} = "Could not export sector !--tower,".$opt->{tower_num}.",".$opt->{sector_name}."--! because there was no up or down rates attached to the sector. Saisei requires a up and down rate be attached to each sector.";
893 return { error => $self->api_error, };
896 my $existing_sector_ap;
897 my $sector_name = $opt->{sector_name};
898 my $location = $opt->{location};
900 #check if sector has been set up as an access point.
901 $existing_sector_ap = $self->api_get_accesspoint($sector_name);
903 # modify the existing accesspoint if changing sector .
904 $self->api_modify_existing_accesspoint (
907 $opt->{sector_uprate_limit},
908 $opt->{sector_downrate_limit},
910 ) if $existing_sector_ap && $opt->{modify_existing};
912 #if sector does not exist as an access point create it.
913 $self->api_create_accesspoint(
915 $opt->{sector_uprate_limit},
916 $opt->{sector_downrate_limit},
918 ) unless $existing_sector_ap;
920 # Attach newly created sector to it's tower.
921 $self->api_modify_accesspoint($sector_name, $opt->{tower_name}, $location) unless ($self->{'__saisei_error'} || $existing_sector_ap);
923 # set access point to existing one or newly created one.
924 my $accesspoint = $existing_sector_ap ? $existing_sector_ap : $self->api_get_accesspoint($sector_name);
926 return { error => $self->api_error, } if $self->api_error;
930 =head2 require_tower_and_sector
932 sets whether the service export requires a sector with it's tower.
936 sub require_tower_and_sector {
940 =head2 tower_sector_required_fields
942 required fields needed for tower and sector export.
946 sub tower_sector_required_fields {
949 'up_rate_limit' => '1',
950 'down_rate_limit' => '1',
953 'up_rate_limit' => '1',
954 'down_rate_limit' => '1',
961 sub required_fields {
962 my @fields = ('svc_broadband__ip_addr_required', 'svc_broadband__speed_up_required', 'svc_broadband__speed_down_required', 'svc_broadband__sectornum_required');
966 sub process_virtual_ap {
967 my ($self, $opt) = @_;
969 my $existing_virtual_ap;
970 my $virtual_name = $opt->{virtual_name};
972 #check if virtual_ap has been set up as an access point.
973 $existing_virtual_ap = $self->api_get_accesspoint($virtual_name);
975 # modify the existing virtual accesspoint if changing it. this should never happen
976 $self->api_modify_existing_accesspoint (
979 $opt->{virtual_uprate_limit},
980 $opt->{virtual_downrate_limit},
981 ) if $existing_virtual_ap && $opt->{modify_existing};
983 #if virtual ap does not exist as an access point create it.
984 $self->api_create_accesspoint(
986 $opt->{virtual_uprate_limit},
987 $opt->{virtual_downrate_limit},
988 ) unless $existing_virtual_ap;
991 if ($existing_virtual_ap && (ref $existing_virtual_ap->{collection}->[0]->{uplink} eq "HASH") && ($existing_virtual_ap->{collection}->[0]->{uplink}->{link}->{name} ne $opt->{sector_name})) {
995 # Attach newly created virtual ap to tower sector ap or if sector has changed.
996 $self->api_modify_accesspoint($virtual_name, $opt->{sector_name}) unless ($self->{'__saisei_error'} || ($existing_virtual_ap && !$update_sector));
998 # set access point to existing one or newly created one.
999 my $accesspoint = $existing_virtual_ap ? $existing_virtual_ap : $self->api_get_accesspoint($virtual_name);
1001 return $accesspoint;
1004 sub export_provisioned_services {
1006 my $param = thaw(decode_base64(shift));
1008 my $part_export = FS::Record::qsearchs('part_export', { 'exportnum' => $param->{export_provisioned_services_exportnum}, } )
1009 or die "You are trying to use an unknown exportnum $param->{export_provisioned_services_exportnum}. This export does not exist.\n";
1012 my @svcparts = FS::Record::qsearch({
1013 'table' => 'export_svc',
1014 'addl_from' => 'LEFT JOIN part_svc USING ( svcpart ) ',
1015 'hashref' => { 'exportnum' => $param->{export_provisioned_services_exportnum}, },
1017 my $part_count = scalar @svcparts;
1019 my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
1021 my @svcs = FS::Record::qsearch({
1022 'table' => 'cust_svc',
1023 'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum ) ',
1024 'extra_sql' => " WHERE svcpart in ('".$parts."')",
1027 my $svc_count = scalar @svcs;
1030 for (my $c=1; $c <=100; $c=$c+1) { $status{int($svc_count * ($c/100))} = $c; }
1032 my $process_count=0;
1033 foreach my $svc (@svcs) {
1034 if ($status{$process_count}) { my $s = $status{$process_count}; $job->update_statustext($s); }
1035 ## check if service exists as host if not export it.
1036 my $host = api_get_host($part_export, $svc->{Hash}->{ip_addr});
1037 die ("Please double check your credentials as ".$host->{message}."\n") if $host->{message};
1038 warn "Exporting service ".$svc->{Hash}->{ip_addr}."\n" if ($part_export->option('debug'));
1039 my $export_error = _export_insert($part_export,$svc) unless $host->{collection};
1040 if ($export_error) {
1041 warn "Error exporting service ".$svc->{Hash}->{ip_addr}."\n" if ($part_export->option('debug'));
1042 die ("$export_error\n");
1051 sub export_all_towers_sectors {
1053 my $param = thaw(decode_base64(shift));
1055 my $part_export = FS::Record::qsearchs('part_export', { 'exportnum' => $param->{export_provisioned_services_exportnum}, } )
1056 or die "You are trying to use an unknown exportnum $param->{export_provisioned_services_exportnum}. This export does not exist.\n";
1059 my @towers = FS::Record::qsearch({
1062 my $tower_count = scalar @towers;
1065 for (my $c=1; $c <=100; $c=$c+1) { $status{int($tower_count * ($c/100))} = $c; }
1067 my $process_count=0;
1068 foreach my $tower (@towers) {
1069 if ($status{$process_count}) { my $s = $status{$process_count}; $job->update_statustext($s); }
1070 my $export_error = export_tower_sector($part_export,$tower);
1071 if ($export_error->{'error'}) {
1072 warn "Error exporting tower/sector (".$tower->{Hash}->{towername}.")\n" if ($part_export->option('debug'));
1073 die ($export_error->{'error'}."\n");
1082 sub test_export_report {
1083 my ($self, $opts) = @_;
1086 ## check all part services for export errors
1087 my @exports = FS::Record::qsearch('part_export', { 'exporttype' => "saisei", } );
1088 my $export_nums = join "', '", map { $_->{Hash}->{exportnum} } @exports;
1090 my $svc_part_export_error;
1091 my @svcparts = FS::Record::qsearch({
1092 'table' => 'part_svc',
1093 'addl_from' => 'LEFT JOIN export_svc USING ( svcpart ) ',
1094 'extra_sql' => " WHERE export_svc.exportnum in ('".$export_nums."')",
1096 my $part_count = scalar @svcparts;
1099 foreach (@svcparts) {
1100 my $part_error->{'description'} = $_->svc;
1101 $part_error->{'link'} = $opts->{'fsurl'}."/edit/part_svc.cgi?".$_->svcpart;
1103 foreach my $s ('speed_up', 'speed_down') {
1104 my $speed = $_->part_svc_column($s);
1105 if ($speed->columnflag eq "" || $speed->columnflag eq "D") {
1106 $part_error->{'errors'}->{$speed->columnname} = "Field ".$speed->columnname." is not set to be required and can be set while provisioning the service." unless $speed->required eq "Y";
1108 elsif ($speed->columnflag eq "F" || $speed->columnflag eq "S") {
1109 $part_error->{'errors'}->{$speed->columnname} = "Field ".$speed->columnname." is set to auto fill while provisioning the service but there is no value set." unless $speed->columnvalue;
1111 elsif ($speed->columnflag eq "P") {
1112 my $fcc_speed_name = "broadband_".$speed->columnvalue."stream";
1113 foreach my $part_pkg ( FS::Record::qsearchs({
1114 'table' => 'part_pkg',
1115 'addl_from' => 'LEFT JOIN pkg_svc USING ( pkgpart ) ',
1116 'extra_sql' => " WHERE pkg_svc.svcpart = ".$_->svcpart,
1118 my $pkglink = '<a href="'.$opts->{'fsurl'}.'/edit/part_pkg.cgi?'.$part_pkg->pkgpart.'"><FONT COLOR="red"><B>'.$part_pkg->pkg.'</B></FONT></a>';
1119 $part_error->{'errors'}->{$speed->columnname} = "Field ".$speed->columnname." is set to package FCC 477 information, but package ".$pkglink." does not have FCC ".$fcc_speed_name." set."
1120 unless $part_pkg->fcc_option($fcc_speed_name);
1124 $part_error->{'errors'}->{'ip_addr'} = "Field IP Address is not set to required" if $_->part_svc_column("ip_addr")->required ne "Y";
1125 $svc_part_error->{$_->svcpart} = $part_error if $part_error->{'errors'};
1128 $svc_part_export_error->{"services"}->{'description'} = "Service definitions";
1129 $svc_part_export_error->{"services"}->{'count'} = $part_count;
1130 $svc_part_export_error->{"services"}->{'errors'} = $svc_part_error if $svc_part_error;
1132 push @export_error, $svc_part_export_error;
1134 ## check all provisioned cust services for export errors
1135 my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
1136 my $cust_svc_export_error;
1137 my @svcs = FS::Record::qsearch({
1138 'table' => 'cust_svc',
1139 'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum ) ',
1140 'extra_sql' => " WHERE svcpart in ('".$parts."')",
1142 my $svc_count = scalar @svcs;
1146 my $svc_error->{'description'} = $_->description;
1147 $svc_error->{'link'} = $opts->{'fsurl'}."/edit/svc_broadband.cgi?".$_->svcnum;
1149 foreach my $s ('speed_up', 'speed_down', 'ip_addr') {
1150 $svc_error->{'errors'}->{$s} = "Field ".$s." is not set and is required for this service to be exported to Saisei." unless $_->$s;
1153 my $sector = FS::Record::qsearchs({
1154 'table' => 'tower_sector',
1155 'extra_sql' => " WHERE sectornum = ".$_->sectornum." AND sectorname != '_default'",
1156 }) if $_->sectornum;
1158 $svc_error->{'errors'}->{'sectornum'} = "No tower sector is set for this service. There needs to be a tower and sector set to be exported to Saisei.";
1161 foreach my $s ('up_rate_limit', 'down_rate_limit') {
1162 $svc_error->{'errors'}->{'sectornum'} = "The sector ".$sector->description." does not have a ".$s." set. The sector needs a ".$s." set to be exported to Saisei."
1166 $cust_svc_error->{$_->svcnum} = $svc_error if $svc_error->{'errors'};
1169 $cust_svc_export_error->{"provisioned_services"}->{'description'} = "Provisioned services";
1170 $cust_svc_export_error->{"provisioned_services"}->{'count'} = $svc_count;
1171 $cust_svc_export_error->{"provisioned_services"}->{'errors'} = $cust_svc_error if $cust_svc_error;
1173 push @export_error, $cust_svc_export_error;
1176 ## check all towers and sectors for export errors
1177 my $tower_sector_export_error;
1178 my @towers = FS::Record::qsearch({
1181 my $tower_count = scalar @towers;
1185 my $tower_error->{'description'} = $_->towername;
1186 $tower_error->{'link'} = $opts->{'fsurl'}."/edit/tower.html?".$_->towernum;
1188 foreach my $s ('up_rate_limit', 'down_rate_limit') {
1189 $tower_error->{'errors'}->{$s} = "Field ".$s." is not set for the tower, this is required for this tower to be exported to Saisei." unless $_->$s;
1192 my @sectors = FS::Record::qsearch({
1193 'table' => 'tower_sector',
1194 'extra_sql' => " WHERE towernum = ".$_->towernum." AND sectorname != '_default' AND (up_rate_limit IS NULL OR down_rate_limit IS NULL)",
1196 foreach my $sector (@sectors) {
1197 foreach my $s ('up_rate_limit', 'down_rate_limit') {
1198 $tower_error->{'errors'}->{'sector_'.$s} = "The sector ".$sector->description." does not have a ".$s." set. The sector needs a ".$s." set to be exported to Saisei."
1202 $towers_error->{$_->towernum} = $tower_error if $tower_error->{'errors'};
1205 $tower_sector_export_error->{"tower_sector"}->{'description'} = "Tower / Sector";
1206 $tower_sector_export_error->{"tower_sector"}->{'count'} = $tower_count;
1207 $tower_sector_export_error->{"tower_sector"}->{'errors'} = $towers_error if $towers_error;
1209 push @export_error, $tower_sector_export_error;
1211 return [@export_error];