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;
353 'tower_name' => $tower_name,
354 'tower_num' => $tower->{Hash}->{towernum},
355 'tower_uprate_limit' => $tower->{Hash}->{up_rate_limit},
356 'tower_downrate_limit' => $tower->{Hash}->{down_rate_limit},
357 'modify_existing' => '1', # modify an existing access point with this info
359 $tower_opt->{'location'} = $tower_location if $tower_location;
361 my $tower_access_point = process_tower($self, $tower_opt);
362 return $tower_access_point if $tower_access_point->{error};
364 #get list of all access points
366 'table' => 'tower_sector',
368 'hashref' => { 'towernum' => $tower->{Hash}->{towernum}, },
371 #for each one modify or create it.
372 foreach my $tower_sector ( FS::Record::qsearch($hash_opt) ) {
373 next if $tower_sector->{Hash}->{sectorname} eq "_default";
374 my $sector_name = $tower_sector->{Hash}->{sectorname};
375 $sector_name =~ s/\s/_/g;
377 'tower_name' => $tower_name,
378 'tower_num' => $tower_sector->{Hash}->{towernum},
379 'sector_name' => $sector_name,
380 'sector_uprate_limit' => $tower_sector->{Hash}->{up_rate_limit},
381 'sector_downrate_limit' => $tower_sector->{Hash}->{down_rate_limit},
382 'modify_existing' => '1', # modify an existing access point with this info
384 $sector_opt->{'location'} = $tower_location if $tower_location;
386 my $sector_access_point = process_sector($self, $sector_opt) unless ($sector_name eq "_default");
387 return $sector_access_point if $sector_access_point->{error};
390 return { error => $self->api_error, };
393 ## creates the rateplan name
394 sub get_rateplan_name {
395 my ($self, $svc_broadband, $svc_name) = @_;
397 my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } ) unless $svc_name;
398 my $service_name = $svc_name ? $svc_name : $service_part->{Hash}->{svc};
400 my $rateplan_name = $service_name . " " . $svc_broadband->{Hash}->{speed_down} . "-" . $svc_broadband->{Hash}->{speed_up};
401 $rateplan_name =~ s/\s/_/g; $rateplan_name =~ s/[^A-Za-z0-9\-_]//g;
403 return $rateplan_name;
408 These methods allow access to the Saisei API using the credentials
409 set in the export options.
415 Accepts I<$method>, I<$path>, I<$params> hashref and optional.
416 Places an api call to the specified path and method with the specified params.
417 Returns the decoded json object returned by the api call.
418 Returns empty on failure; retrieve error messages using L</api_error>.
423 my ($self,$method,$path,$params) = @_;
425 $self->{'__saisei_error'} = '';
426 my $auth_info = $self->option('username') . ':' . $self->option('password');
429 warn "Calling $method on http://"
430 .$self->{Hash}->{machine}.':'.$self->option('port')
431 ."/rest/top/configurations/running/$path\n" if $self->option('debug');
433 my $data = encode_json($params) if keys %{ $params };
435 my $client = REST::Client->new();
436 $client->addHeader("Authorization", "Basic ".encode_base64($auth_info));
437 $client->setHost('http://'.$self->{Hash}->{machine}.':'.$self->option('port'));
438 $client->$method('/rest/top/configurations/running'.$path, $data, { "Content-type" => 'application/json'});
440 warn "Saisei Response Code is ".$client->responseCode()."\n" if $self->option('debug');
444 if ($client->responseCode() eq '200' || $client->responseCode() eq '201') {
445 eval { $result = decode_json($client->responseContent()) };
447 $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.";
448 warn "Saisei RC 201 Response Content is not json\n".$client->responseContent()."\n" if $self->option('debug');
452 elsif ($client->responseCode() eq '404') {
453 eval { $result = decode_json($client->responseContent()) };
455 $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.";
456 warn "Saisei RC 404 Response Content is not json\n".$client->responseContent()."\n" if $self->option('debug');
459 ## check if message is for empty hash.
460 my($does_not_exist) = $result->{message} =~ /'(.*)' does not exist$/;
461 $self->{'__saisei_error'} = "Saisei Error: ".$result->{message} unless $does_not_exist;
462 warn "Saisei Response Content is\n".$client->responseContent."\n" if ($self->option('debug') && !$does_not_exist);
465 elsif ($client->responseCode() eq '500') {
466 $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();
467 warn "Saisei Response Content is\n".$client->responseContent."\n" if $self->option('debug');
471 $self->{'__saisei_error'} = "Received Bad response from server during $method $path $data, we received responce code: " . $client->responseCode() . " " . $client->responseContent;
472 warn "Saisei Response Content is\n".$client->responseContent."\n" if $self->option('debug');
482 Returns the error string set by L</Saisei API> methods,
483 or a blank string if most recent call produced no errors.
489 return $self->{'__saisei_error'} || '';
492 =head2 api_get_policies
494 Gets a list of global policies.
498 sub api_get_policies {
501 my $get_policies = $self->api_call("GET", '/policies/?token=1&order=name&start=0&limit=20&select=name%2Cpercent_rate%2Cassured%2C');
502 return if $self->api_error;
503 $self->{'__saisei_error'} = "Did not receive any global policies from Saisei."
504 unless $get_policies;
506 return $get_policies->{collection};
509 =head2 api_get_rateplan
511 Gets rateplan info for specific rateplan.
515 sub api_get_rateplan {
517 my $rateplan = shift;
519 my $get_rateplan = $self->api_call("GET", "/rate_plans/$rateplan");
520 return if $self->api_error;
522 return $get_rateplan;
527 Gets user info for specific user.
535 my $get_user = $self->api_call("GET", "/users/$user");
536 return if $self->api_error;
541 =head2 api_get_accesspoint
543 Gets user info for specific access point.
547 sub api_get_accesspoint {
549 my $accesspoint = shift;
551 my $get_accesspoint = $self->api_call("GET", "/access_points/$accesspoint");
552 return if $self->api_error;
554 return $get_accesspoint;
559 Gets user info for specific host.
567 my $get_host = $self->api_call("GET", "/hosts/$ip");
569 return { message => $self->api_error, } if $self->api_error;
574 =head2 api_create_rateplan
580 sub api_create_rateplan {
581 my ($self, $svc, $rateplan) = @_;
583 $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};
584 $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};
586 my $new_rateplan = $self->api_call(
588 "/rate_plans/$rateplan",
590 'downstream_rate' => $svc->{Hash}->{speed_down},
591 'upstream_rate' => $svc->{Hash}->{speed_up},
593 ) unless $self->{'__saisei_error'};
595 $self->{'__saisei_error'} = "Saisei could not create the rate plan $rateplan."
596 unless ($new_rateplan || $self->{'__saisei_error'});
598 return $new_rateplan;
602 =head2 api_modify_rateplan
604 Modify a new rateplan.
608 sub api_modify_rateplan {
609 my ($self,$svc,$rateplan_name) = @_;
612 my $policies = $self->api_get_policies();
614 foreach my $policy (@$policies) {
615 my $policyname = $policy->{name};
616 my $rate_multiplier = '';
617 if ($policy->{background}) { $rate_multiplier = ".01"; }
618 my $modified_rateplan = $self->api_call(
620 "/rate_plans/$rateplan_name/partitions/$policyname",
622 'restricted' => $policy->{assured}, # policy_assured_flag
623 'rate_multiplier' => $rate_multiplier, # policy_background 0.1
624 'rate' => $policy->{percent_rate}, # policy_percent_rate
628 $self->{'__saisei_error'} = "Saisei could not modify the rate plan $rateplan_name after it was created."
629 unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
637 =head2 api_modify_existing_rateplan
639 Modify a existing rateplan.
643 sub api_modify_existing_rateplan {
644 my ($self,$svc,$rateplan_name) = @_;
646 $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};
647 $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};
649 my $modified_rateplan = $self->api_call(
651 "/rate_plans/$rateplan_name",
653 'downstream_rate' => $svc->{Hash}->{speed_down},
654 'upstream_rate' => $svc->{Hash}->{speed_up},
658 $self->{'__saisei_error'} = "Saisei could not modify the rate plan $rateplan_name."
659 unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
665 =head2 api_create_user
671 sub api_create_user {
672 my ($self,$user, $description, $location) = @_;
675 'description' => $description,
677 $user_hash->{'map_location'} = $location if $location;
679 my $new_user = $self->api_call(
685 $self->{'__saisei_error'} = "Saisei could not create the user $user"
686 unless ($new_user || $self->{'__saisei_error'}); # should never happen
692 =head2 api_create_accesspoint
694 Creates a access point.
698 sub api_create_accesspoint {
699 my ($self,$accesspoint, $upratelimit, $downratelimit, $location) = @_;
702 'downstream_rate_limit' => $downratelimit,
703 'upstream_rate_limit' => $upratelimit,
704 'interface' => $self->option('interface'),
706 $ap_hash->{'map_location'} = $location if $location;
708 my $new_accesspoint = $self->api_call(
710 "/access_points/$accesspoint",
714 $self->{'__saisei_error'} = "Saisei could not create the access point $accesspoint"
715 unless ($new_accesspoint || $self->{'__saisei_error'}); # should never happen
720 =head2 api_modify_accesspoint
722 Modify a new access point.
726 sub api_modify_accesspoint {
727 my ($self, $accesspoint, $uplink, $location) = @_;
731 'interface' => $self->option('interface'),
733 $ap_hash->{'map_location'} = $location if $location;
735 my $modified_accesspoint = $self->api_call(
737 "/access_points/$accesspoint",
741 $self->{'__saisei_error'} = "Saisei could not modify the access point $accesspoint after it was created."
742 unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
748 =head2 api_modify_existing_accesspoint
750 Modify a existing accesspoint.
754 sub api_modify_existing_accesspoint {
755 my ($self, $accesspoint, $uplink, $upratelimit, $downratelimit, $location) = @_;
758 'downstream_rate_limit' => $downratelimit,
759 'upstream_rate_limit' => $upratelimit,
760 'interface' => $self->option('interface'),
761 # 'uplink' => $uplink, # name of attached access point
763 $ap_hash->{'map_location'} = $location if $location;
765 my $modified_accesspoint = $self->api_call(
767 "/access_points/$accesspoint",
771 $self->{'__saisei_error'} = "Saisei could not modify the access point $accesspoint."
772 unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
778 =head2 api_add_host_to_user
780 ties host to user, rateplan and default access point.
784 sub api_add_host_to_user {
785 # my ($self,$user, $rateplan, $ip, $accesspoint, $location) = @_;
786 my ($self,$opt) = @_;
787 my $ip = $opt->{'ip'};
788 my $location = $opt->{'location'};
791 'user' => $opt->{'user'},
792 'rate_plan' => $opt->{'rateplan'},
793 'access_point' => $opt->{'accesspoint'},
795 $newhost_hash->{'map_location'} = $location if $location;
797 my $new_host = $self->api_call(
803 $self->{'__saisei_error'} = "Saisei could not create the host $ip"
804 unless ($new_host || $self->{'__saisei_error'}); # should never happen
810 =head2 api_delete_host_to_user
812 unties host from user and rateplan.
813 this will set the host entry at Saisei to the default rate plan with the user and access point set to <none>.
817 sub api_delete_host_to_user {
818 my ($self,$user, $rateplan, $ip) = @_;
820 my $default_rate_plan = $self->api_call("GET", '?token=1&select=default_rate_plan');
821 return if $self->api_error;
822 $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."
823 unless $default_rate_plan;
825 my $default_rateplan_name = $default_rate_plan->{collection}->[0]->{default_rate_plan}->{link}->{name};
827 my $delete_host = $self->api_call(
832 'access_point' => '<none>',
833 'rate_plan' => $default_rateplan_name,
837 $self->{'__saisei_error'} = "Saisei could not delete the host $ip"
838 unless ($delete_host || $self->{'__saisei_error'}); # should never happen
845 my ($self, $opt) = @_;
847 if (!$opt->{tower_uprate_limit} || !$opt->{tower_downrate_limit}) {
848 $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.";
849 return { error => $self->api_error, };
852 my $existing_tower_ap;
853 my $tower_name = $opt->{tower_name};
854 my $location = $opt->{location};
856 #check if tower has been set up as an access point.
857 $existing_tower_ap = $self->api_get_accesspoint($tower_name) unless $self->{'__saisei_error'};
859 # modify the existing accesspoint if changing tower .
860 $self->api_modify_existing_accesspoint (
862 '', # tower does not have a uplink on sectors.
863 $opt->{tower_uprate_limit},
864 $opt->{tower_downrate_limit},
866 ) if $existing_tower_ap->{collection} && $opt->{modify_existing};
868 #if tower does not exist as an access point create it.
869 $self->api_create_accesspoint(
871 $opt->{tower_uprate_limit},
872 $opt->{tower_downrate_limit},
874 ) unless $existing_tower_ap->{collection};
876 my $accesspoint = $self->api_get_accesspoint($tower_name);
878 return { error => $self->api_error, } if $self->api_error;
883 my ($self, $opt) = @_;
885 if (!$opt->{sector_name} || $opt->{sector_name} eq '_default') {
886 $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.";
887 return { error => $self->api_error, };
890 if (!$opt->{sector_uprate_limit} || !$opt->{sector_downrate_limit}) {
891 $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.";
892 return { error => $self->api_error, };
895 my $existing_sector_ap;
896 my $sector_name = $opt->{sector_name};
897 my $location = $opt->{location};
899 #check if sector has been set up as an access point.
900 $existing_sector_ap = $self->api_get_accesspoint($sector_name);
902 # modify the existing accesspoint if changing sector .
903 $self->api_modify_existing_accesspoint (
906 $opt->{sector_uprate_limit},
907 $opt->{sector_downrate_limit},
909 ) if $existing_sector_ap && $opt->{modify_existing};
911 #if sector does not exist as an access point create it.
912 $self->api_create_accesspoint(
914 $opt->{sector_uprate_limit},
915 $opt->{sector_downrate_limit},
917 ) unless $existing_sector_ap;
919 # Attach newly created sector to it's tower.
920 $self->api_modify_accesspoint($sector_name, $opt->{tower_name}, $location) unless ($self->{'__saisei_error'} || $existing_sector_ap);
922 # set access point to existing one or newly created one.
923 my $accesspoint = $existing_sector_ap ? $existing_sector_ap : $self->api_get_accesspoint($sector_name);
925 return { error => $self->api_error, } if $self->api_error;
929 =head2 require_tower_and_sector
931 sets whether the service export requires a sector with it's tower.
935 sub require_tower_and_sector {
939 =head2 tower_sector_required_fields
941 required fields needed for tower and sector export.
945 sub tower_sector_required_fields {
948 'up_rate_limit' => '1',
949 'down_rate_limit' => '1',
952 'up_rate_limit' => '1',
953 'down_rate_limit' => '1',
960 sub required_fields {
961 my @fields = ('svc_broadband__ip_addr_required', 'svc_broadband__speed_up_required', 'svc_broadband__speed_down_required', 'svc_broadband__sectornum_required');
965 sub process_virtual_ap {
966 my ($self, $opt) = @_;
968 my $existing_virtual_ap;
969 my $virtual_name = $opt->{virtual_name};
971 #check if virtual_ap has been set up as an access point.
972 $existing_virtual_ap = $self->api_get_accesspoint($virtual_name);
974 # modify the existing virtual accesspoint if changing it. this should never happen
975 $self->api_modify_existing_accesspoint (
978 $opt->{virtual_uprate_limit},
979 $opt->{virtual_downrate_limit},
980 ) if $existing_virtual_ap && $opt->{modify_existing};
982 #if virtual ap does not exist as an access point create it.
983 $self->api_create_accesspoint(
985 $opt->{virtual_uprate_limit},
986 $opt->{virtual_downrate_limit},
987 ) unless $existing_virtual_ap;
990 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})) {
994 # Attach newly created virtual ap to tower sector ap or if sector has changed.
995 $self->api_modify_accesspoint($virtual_name, $opt->{sector_name}) unless ($self->{'__saisei_error'} || ($existing_virtual_ap && !$update_sector));
997 # set access point to existing one or newly created one.
998 my $accesspoint = $existing_virtual_ap ? $existing_virtual_ap : $self->api_get_accesspoint($virtual_name);
1000 return $accesspoint;
1003 sub export_provisioned_services {
1005 my $param = thaw(decode_base64(shift));
1007 my $part_export = FS::Record::qsearchs('part_export', { 'exportnum' => $param->{export_provisioned_services_exportnum}, } )
1008 or die "You are trying to use an unknown exportnum $param->{export_provisioned_services_exportnum}. This export does not exist.\n";
1011 my @svcparts = FS::Record::qsearch({
1012 'table' => 'export_svc',
1013 'addl_from' => 'LEFT JOIN part_svc USING ( svcpart ) ',
1014 'hashref' => { 'exportnum' => $param->{export_provisioned_services_exportnum}, },
1016 my $part_count = scalar @svcparts;
1018 my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
1020 my @svcs = FS::Record::qsearch({
1021 'table' => 'cust_svc',
1022 'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum ) ',
1023 'extra_sql' => " WHERE svcpart in ('".$parts."')",
1026 my $svc_count = scalar @svcs;
1029 for (my $c=1; $c <=100; $c=$c+1) { $status{int($svc_count * ($c/100))} = $c; }
1031 my $process_count=0;
1032 foreach my $svc (@svcs) {
1033 if ($status{$process_count}) { my $s = $status{$process_count}; $job->update_statustext($s); }
1034 ## check if service exists as host if not export it.
1035 my $host = api_get_host($part_export, $svc->{Hash}->{ip_addr});
1036 die ("Please double check your credentials as ".$host->{message}."\n") if $host->{message};
1037 warn "Exporting service ".$svc->{Hash}->{ip_addr}."\n" if ($part_export->option('debug'));
1038 my $export_error = _export_insert($part_export,$svc) unless $host->{collection};
1039 if ($export_error) {
1040 warn "Error exporting service ".$svc->{Hash}->{ip_addr}."\n" if ($part_export->option('debug'));
1041 die ("$export_error\n");
1050 sub export_all_towers_sectors {
1054 my $part_export = FS::Record::qsearchs('part_export', { 'exportnum' => $param->{export_provisioned_services_exportnum}, } )
1055 or die "You are trying to use an unknown exportnum $param->{export_provisioned_services_exportnum}. This export does not exist.\n";
1058 my @towers = FS::Record::qsearch({
1061 my $tower_count = scalar @towers;
1064 for (my $c=1; $c <=100; $c=$c+1) { $status{int($tower_count * ($c/100))} = $c; }
1066 my $process_count=0;
1067 foreach my $tower (@towers) {
1068 if ($status{$process_count}) { my $s = $status{$process_count}; $job->update_statustext($s); }
1069 my $export_error = export_tower_sector($part_export,$tower);
1070 if ($export_error->{'error'}) {
1071 warn "Error exporting tower/sector (".$tower->{Hash}->{towername}.")\n" if ($part_export->option('debug'));
1072 die ($export_error->{'error'}."\n");
1081 sub test_export_report {
1082 my ($self, $opts) = @_;
1085 ## check all part services for export errors
1086 my @exports = FS::Record::qsearch('part_export', { 'exporttype' => "saisei", } );
1087 my $export_nums = join "', '", map { $_->{Hash}->{exportnum} } @exports;
1089 my $svc_part_export_error;
1090 my @svcparts = FS::Record::qsearch({
1091 'table' => 'part_svc',
1092 'addl_from' => 'LEFT JOIN export_svc USING ( svcpart ) ',
1093 'extra_sql' => " WHERE export_svc.exportnum in ('".$export_nums."')",
1095 my $part_count = scalar @svcparts;
1098 foreach (@svcparts) {
1099 my $part_error->{'description'} = $_->svc;
1100 $part_error->{'link'} = $opts->{'fsurl'}."/edit/part_svc.cgi?".$_->svcpart;
1102 foreach my $s ('speed_up', 'speed_down') {
1103 my $speed = $_->part_svc_column($s);
1104 if ($speed->columnflag eq "" || $speed->columnflag eq "D") {
1105 $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";
1107 elsif ($speed->columnflag eq "F" || $speed->columnflag eq "S") {
1108 $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;
1110 elsif ($speed->columnflag eq "P") {
1111 my $fcc_speed_name = "broadband_".$speed->columnvalue."stream";
1112 foreach my $part_pkg ( FS::Record::qsearchs({
1113 'table' => 'part_pkg',
1114 'addl_from' => 'LEFT JOIN pkg_svc USING ( pkgpart ) ',
1115 'extra_sql' => " WHERE pkg_svc.svcpart = ".$_->svcpart,
1117 my $pkglink = '<a href="'.$opts->{'fsurl'}.'/edit/part_pkg.cgi?'.$part_pkg->pkgpart.'"><FONT COLOR="red"><B>'.$part_pkg->pkg.'</B></FONT></a>';
1118 $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."
1119 unless $part_pkg->fcc_option($fcc_speed_name);
1123 $part_error->{'errors'}->{'ip_addr'} = "Field IP Address is not set to required" if $_->part_svc_column("ip_addr")->required ne "Y";
1124 $svc_part_error->{$_->svcpart} = $part_error if $part_error->{'errors'};
1127 $svc_part_export_error->{"services"}->{'description'} = "Service definitions";
1128 $svc_part_export_error->{"services"}->{'count'} = $part_count;
1129 $svc_part_export_error->{"services"}->{'errors'} = $svc_part_error if $svc_part_error;
1131 push @export_error, $svc_part_export_error;
1133 ## check all provisioned cust services for export errors
1134 my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
1135 my $cust_svc_export_error;
1136 my @svcs = FS::Record::qsearch({
1137 'table' => 'cust_svc',
1138 'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum ) ',
1139 'extra_sql' => " WHERE svcpart in ('".$parts."')",
1141 my $svc_count = scalar @svcs;
1145 my $svc_error->{'description'} = $_->description;
1146 $svc_error->{'link'} = $opts->{'fsurl'}."/edit/svc_broadband.cgi?".$_->svcnum;
1148 foreach my $s ('speed_up', 'speed_down', 'ip_addr') {
1149 $svc_error->{'errors'}->{$s} = "Field ".$s." is not set and is required for this service to be exported to Saisei." unless $_->$s;
1152 my $sector = FS::Record::qsearchs({
1153 'table' => 'tower_sector',
1154 'extra_sql' => " WHERE sectornum = ".$_->sectornum." AND sectorname != '_default'",
1155 }) if $_->sectornum;
1157 $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.";
1160 foreach my $s ('up_rate_limit', 'down_rate_limit') {
1161 $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."
1165 $cust_svc_error->{$_->svcnum} = $svc_error if $svc_error->{'errors'};
1168 $cust_svc_export_error->{"provisioned_services"}->{'description'} = "Provisioned services";
1169 $cust_svc_export_error->{"provisioned_services"}->{'count'} = $svc_count;
1170 $cust_svc_export_error->{"provisioned_services"}->{'errors'} = $cust_svc_error if $cust_svc_error;
1172 push @export_error, $cust_svc_export_error;
1175 ## check all towers and sectors for export errors
1176 my $tower_sector_export_error;
1177 my @towers = FS::Record::qsearch({
1180 my $tower_count = scalar @towers;
1184 my $tower_error->{'description'} = $_->towername;
1185 $tower_error->{'link'} = $opts->{'fsurl'}."/edit/tower.html?".$_->towernum;
1187 foreach my $s ('up_rate_limit', 'down_rate_limit') {
1188 $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;
1191 my @sectors = FS::Record::qsearch({
1192 'table' => 'tower_sector',
1193 'extra_sql' => " WHERE towernum = ".$_->towernum." AND sectorname != '_default' AND (up_rate_limit IS NULL OR down_rate_limit IS NULL)",
1195 foreach my $sector (@sectors) {
1196 foreach my $s ('up_rate_limit', 'down_rate_limit') {
1197 $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."
1201 $towers_error->{$_->towernum} = $tower_error if $tower_error->{'errors'};
1204 $tower_sector_export_error->{"tower_sector"}->{'description'} = "Tower / Sector";
1205 $tower_sector_export_error->{"tower_sector"}->{'count'} = $tower_count;
1206 $tower_sector_export_error->{"tower_sector"}->{'errors'} = $towers_error if $towers_error;
1208 push @export_error, $tower_sector_export_error;
1210 return [@export_error];