1 package FS::part_export::saisei;
4 use vars qw( @ISA %info );
5 use base qw( FS::part_export );
6 use Date::Format 'time2str';
17 FS::part_export::saisei
21 Saisei integration for Freeside
25 This export offers basic svc_broadband provisioning for Saisei.
27 This is a customer integration with Saisei. This will setup a rate plan and tie
28 the rate plan to a host and access point via the Saisei API when the broadband service is provisioned.
29 It will also untie the rate plan via the API upon unprovisioning of the broadband service.
31 Add a new export and fill out required fields:
33 <LI>Hostname or IP - <I>Host name to Saisei API</I></LI>
34 <LI>Port - <I>Port number to Saisei API</I></LI>
35 <LI>User Name - <I>Saisei API user name</I></LI>
36 <LI>Password - <I>Saisei API password</I></LI>
38 Create a broadband service. The broadband service name will become the Saisei rate plan name.
39 Set the upload and download speed, and set the modifier to fixed.
40 Set IP Address to required.
41 Attach Saisei export to service
43 Create a tower and add a sector to that tower. The sector name will be the name of the access point,
44 Make sure you have set an up and down rate for the Tower and Sector.
46 When you provision the service, enter the ip address associated to this service.
47 Select the Tower and Sector for it's access point.
49 When the service is provisioned it will auto setup the rate plan.
51 This module also provides generic methods for working through the L</Saisei API>.
55 tie my %options, 'Tie::IxHash',
56 'port' => { label => 'Port',
58 'username' => { label => 'User Name',
60 'password' => { label => 'Password',
62 'debug' => { type => 'checkbox',
63 label => 'Enable debug warnings' },
67 'svc' => 'svc_broadband',
68 'desc' => 'Export broadband service/account to Saisei',
69 'options' => \%options,
71 This is a customer integration with Saisei. This will setup a rate plan and tie
72 the rate plan to a host and access point via the Saisei API when the broadband service is provisioned.
73 It will also untie the rate plan via the API upon unprovisioning of the broadband service.
75 Add a new export and fill out required fields:
77 <LI>Hostname or IP - <I>Host name to Saisei API</I></LI>
78 <LI>Port - <I>Port number to Saisei API</I></LI>
79 <LI>User Name - <I>Saisei API user name</I></LI>
80 <LI>Password - <I>Saisei API password</I></LI>
82 Create a broadband service. The broadband service name will become the Saisei rate plan name.
83 Set the upload and download speed, and set the modifier to fixed.
84 Set IP Address to required.
85 Attach Saisei export to service
87 Create a tower and add a sector to that tower. The sector name will be the name of the access point,
88 Make sure you have set an up and down rate for the Tower and Sector.
90 When you provision the service, enter the ip address associated to this service.
91 Select the Tower and Sector for it's access point.
93 When the service is provisioned it will auto setup the rate plan.
98 my ($self, $svc_broadband) = @_;
100 my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } );
101 my $rateplan_name = $service_part->{Hash}->{svc};
102 $rateplan_name =~ s/\s/_/g;
104 # load needed info from our end
105 my $cust_main = $svc_broadband->cust_main;
106 return "Could not load service customer" unless $cust_main;
107 my $conf = new FS::Conf;
110 my $policies = $self->api_get_policies();
112 # check for existing rate plan
113 my $existing_rateplan;
114 $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
116 # if no existing rate plan create one and modify it.
117 $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
118 $self->api_modify_rateplan($policies->{collection}, $svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
120 # set rateplan to existing one or newly created one.
121 my $rateplan = $existing_rateplan ? $existing_rateplan : $self->api_get_rateplan($rateplan_name);
123 my $username = $svc_broadband->{Hash}->{svcnum};
124 my $description = $svc_broadband->{Hash}->{description};
127 $self->{'__saisei_error'} = 'no username - can not export';
128 warn "No user $username\n" if $self->option('debug');
129 return $self->api_error;
132 # check for existing user.
134 $existing_user = $self->api_get_user($username) unless $self->{'__saisei_error'};
136 # if no existing user create one.
137 $self->api_create_user($username, $description) unless $existing_user;
139 # set user to existing one or newly created one.
140 my $user = $existing_user ? $existing_user : $self->api_get_user($username);
142 ## add access point ?
143 my $tower_sector = FS::Record::qsearchs({
144 'table' => 'tower_sector',
145 'select' => 'tower.towername,
146 tower.up_rate as toweruprate,
147 tower.down_rate as towerdownrate,
148 tower_sector.sectorname,
149 tower_sector.up_rate as sectoruprate,
150 tower_sector.down_rate as sectordownrate ',
151 'addl_from' => 'LEFT JOIN tower USING ( towernum )',
153 'sectornum' => $svc_broadband->{Hash}->{sectornum},
157 my $existing_tower_ap;
158 my $tower_name = $tower_sector->{Hash}->{towername};
159 $tower_name =~ s/\s/_/g;
161 #check if tower has been set up as an access point.
162 $existing_tower_ap = $self->api_get_accesspoint($tower_name) unless $self->{'__saisei_error'};;
164 #if tower does not exist as an access point create it.
165 $self->api_create_accesspoint(
167 $tower_sector->{Hash}->{toweruprate},
168 $tower_sector->{Hash}->{towerdownrate}
169 ) unless $existing_tower_ap;
171 my $existing_sector_ap;
172 my $sector_name = $tower_sector->{Hash}->{sectorname};
173 $sector_name =~ s/\s/_/g;
175 #check if sector has been set up as an access point.
176 $existing_sector_ap = $self->api_get_accesspoint($sector_name);
178 #if sector does not exist as an access point create it.
179 $self->api_create_accesspoint(
181 $tower_sector->{Hash}->{sectoruprate},
182 $tower_sector->{Hash}->{sectordownrate},
184 ) unless $existing_sector_ap;
186 # Attach newly created sector to it's tower.
187 $self->api_modify_accesspoint($sector_name, $tower_name) unless ($self->{'__saisei_error'} || $existing_sector_ap);
189 # set access point to existing one or newly created one.
190 my $accesspoint = $existing_sector_ap ? $existing_sector_ap : $self->api_get_accesspoint($sector_name);
192 ## tie host to user add sector name as access point.
193 $self->api_add_host_to_user(
194 $user->{collection}->[0]->{name},
195 $rateplan->{collection}->[0]->{name},
196 $svc_broadband->{Hash}->{ip_addr},
197 $accesspoint->{collection}->[0]->{name},
198 ) unless $self->{'__saisei_error'};
201 return $self->api_error;
205 sub _export_replace {
206 my ($self, $svc_phone) = @_;
211 my ($self, $svc_broadband) = @_;
213 my $cust_main = $svc_broadband->cust_main;
214 return "Could not load service customer" unless $cust_main;
215 my $conf = new FS::Conf;
217 my $rateplan_name = $svc_broadband->{Hash}->{description};
218 $rateplan_name =~ s/\s/_/g;
220 my @email = map { $_->emailaddress } FS::Record::qsearch({
221 'table' => 'cust_contact',
222 'select' => 'emailaddress',
223 'addl_from' => ' JOIN contact_email USING (contactnum)',
224 'hashref' => { 'custnum' => $cust_main->{Hash}->{custnum}, },
226 my $username = $email[0];
229 $self->api_delete_host_to_user($username, $rateplan_name, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
234 sub _export_suspend {
235 my ($self, $svc_phone) = @_;
239 sub _export_unsuspend {
240 my ($self, $svc_phone) = @_;
246 These methods allow access to the Saisei API using the credentials
247 set in the export options.
253 Accepts I<$method>, I<$path>, I<$params> hashref and optional.
254 Places an api call to the specified path and method with the specified params.
255 Returns the decoded json object returned by the api call.
256 Returns empty on failure; retrieve error messages using L</api_error>.
261 my ($self,$method,$path,$params) = @_;
262 $self->{'__saisei_error'} = '';
263 my $auth_info = $self->option('username') . ':' . $self->option('password');
266 warn "Calling $method on http://"
267 .$self->{Hash}->{machine}.':'.$self->option('port')
268 ."/rest/stm/configurations/running/$path\n" if $self->option('debug');
270 my $data = encode_json($params) if keys %{ $params };
272 my $client = REST::Client->new();
273 $client->addHeader("Authorization", "Basic ".encode_base64($auth_info));
274 $client->setHost('http://'.$self->{Hash}->{machine}.':'.$self->option('port'));
275 $client->$method('/rest/stm/configurations/running'.$path, $data, { "Content-type" => 'application/json'});
277 warn "Response Code is ".$client->responseCode()."\n" if $self->option('debug');
281 if ($client->responseCode() eq '200' || $client->responseCode() eq '201') {
282 eval { $result = decode_json($client->responseContent()) };
284 $self->{'__saisei_error'} = "Error decoding json: $@";
289 $self->{'__saisei_error'} = "Bad response from server during $method: " . $client->responseContent();
290 warn "Response Content is\n".$client->responseContent."\n" if $self->option('debug');
300 Returns the error string set by L</Saisei API> methods,
301 or a blank string if most recent call produced no errors.
307 return $self->{'__saisei_error'} || '';
310 =head2 api_get_policies
312 Gets a list of global policies.
316 sub api_get_policies {
319 my $get_policies = $self->api_call("GET", '/policies/?token=1&order=name&start=0&limit=20&select=name%2Cpercent_rate%2Cassured%2C');
320 return if $self->api_error;
321 $self->{'__saisei_error'} = "Did not receive any global policies"
322 unless $get_policies;
324 return $get_policies;
327 =head2 api_get_rateplan
329 Gets rateplan info for specific rateplan.
333 sub api_get_rateplan {
335 my $rateplan = shift;
337 my $get_rateplan = $self->api_call("GET", "/rate_plans/$rateplan");
338 return if $self->api_error;
339 $self->{'__saisei_error'} = "Did not receive any rateplan info"
340 unless $get_rateplan;
342 return $get_rateplan;
347 Gets user info for specific user.
355 my $get_user = $self->api_call("GET", "/users/$user");
356 return if $self->api_error;
357 $self->{'__saisei_error'} = "Did not receive any user info"
363 =head2 api_get_accesspoint
365 Gets user info for specific access point.
369 sub api_get_accesspoint {
371 my $accesspoint = shift;
373 my $get_accesspoint = $self->api_call("GET", "/access_points/$accesspoint");
374 return if $self->api_error;
375 $self->{'__saisei_error'} = "Did not receive any access point info"
376 unless $get_accesspoint;
378 return $get_accesspoint;
381 =head2 api_create_rateplan
387 sub api_create_rateplan {
388 my ($self, $svc, $rateplan) = @_;
390 my $new_rateplan = $self->api_call(
392 "/rate_plans/$rateplan",
394 'downstream_rate' => $svc->{Hash}->{speed_down},
395 'upstream_rate' => $svc->{Hash}->{speed_up},
399 $self->{'__saisei_error'} = "Rate Plan not created"
400 unless $new_rateplan; # should never happen
401 return $new_rateplan;
405 =head2 api_modify_rateplan
411 sub api_modify_rateplan {
412 my ($self,$policies,$svc,$rateplan_name) = @_;
414 foreach my $policy (@$policies) {
415 my $policyname = $policy->{name};
416 my $rate_multiplier = '';
417 if ($policy->{background}) { $rate_multiplier = ".01"; }
418 my $modified_rateplan = $self->api_call(
420 "/rate_plans/$rateplan_name/partitions/$policyname",
422 'restricted' => $policy->{assured}, # policy_assured_flag
423 'rate_multiplier' => $rate_multiplier, # policy_background 0.1
424 'rate' => $policy->{percent_rate}, # policy_percent_rate
428 $self->{'__saisei_error'} = "Rate Plan not modified"
429 unless $modified_rateplan; # should never happen
437 =head2 api_create_user
443 sub api_create_user {
444 my ($self,$user, $description) = @_;
446 my $new_user = $self->api_call(
450 'description' => $description,
454 $self->{'__saisei_error'} = "User not created"
455 unless $new_user; # should never happen
461 =head2 api_create_accesspoint
463 Creates a access point.
467 sub api_create_accesspoint {
468 my ($self,$accesspoint, $uprate, $downrate) = @_;
470 # this has not been tested, but should work, if needed.
471 my $new_accesspoint = $self->api_call(
473 "/access_points/$accesspoint",
475 'downstream_rate_limit' => $downrate,
476 'upstream_rate_limit' => $uprate,
480 $self->{'__saisei_error'} = "Access point not created"
481 unless $new_accesspoint; # should never happen
486 =head2 api_modify_accesspoint
488 Modify a access point.
492 sub api_modify_accesspoint {
493 my ($self, $accesspoint, $uplink) = @_;
495 my $modified_rateplan = $self->api_call(
497 "/access_points/$accesspoint",
499 'uplink' => $uplink, # name of attached access point
503 $self->{'__saisei_error'} = "Rate Plan not modified"
504 unless $modified_rateplan; # should never happen
510 =head2 api_add_host_to_user
512 ties host to user, rateplan and default access point.
516 sub api_add_host_to_user {
517 my ($self,$user, $rateplan, $ip, $accesspoint) = @_;
519 my $new_host = $self->api_call(
524 'rate_plan' => $rateplan,
525 'access_point' => $accesspoint,
529 $self->{'__saisei_error'} = "Host not created"
530 unless $new_host; # should never happen
536 =head2 api_delete_host_to_user
538 unties host to user and rateplan.
542 sub api_delete_host_to_user {
543 my ($self,$user, $rateplan, $ip) = @_;
545 my $default_rate_plan = $self->api_call("GET", '?token=1&select=default_rate_plan');
546 return if $self->api_error;
547 $self->{'__saisei_error'} = "Did not receive a default rate plan"
548 unless $default_rate_plan;
550 my $default_rateplan_name = $default_rate_plan->{collection}->[0]->{default_rate_plan}->{link}->{name};
552 my $delete_host = $self->api_call(
557 'access_point' => '<none>',
558 'rate_plan' => $default_rateplan_name,
562 $self->{'__saisei_error'} = "Host not created"
563 unless $delete_host; # should never happen