RT# 78356 - cleaned up code and added debug code
[freeside.git] / FS / FS / part_export / saisei.pm
1 package FS::part_export::saisei;
2
3 use strict;
4 use vars qw( @ISA %info );
5 use base qw( FS::part_export );
6 use Date::Format 'time2str';
7 use Cpanel::JSON::XS;
8 use MIME::Base64;
9 use REST::Client;
10 use Data::Dumper;
11 use FS::Conf;
12
13 #@ISA = qw( FS::part_export::http );
14
15 =pod
16
17 =head1 NAME
18
19 FS::part_export::saisei
20
21 =head1 SYNOPSIS
22
23 Saisei integration for Freeside
24
25 =head1 DESCRIPTION
26
27 This export offers basic svc_broadband provisioning for Saisei.
28
29 This module also provides generic methods for working through the L</Saisei API>.
30
31 =cut
32
33 tie my %options, 'Tie::IxHash',
34   'port'             => { label => 'Port',
35                           default => 5000 },
36   'username'         => { label => 'User Name',
37                           default => '' },
38   'password'         => { label => 'Password',
39                           default => '' },
40   'debug'            => { type => 'checkbox',
41                           label => 'Enable debug warnings' },
42 ;
43
44 %info = (
45   'svc'             => 'svc_broadband',
46   'desc'            => 'Export broadband service/account to Saisei',
47   'options'         => \%options,
48   'notes'           => <<'END',
49 This is customer integration with Saisei.
50 END
51 );
52
53 #"/STM_IP:5000/rest/top/configurations/running/" is for http 5029 for https
54
55 #Creating User Names
56 #Users are tracked by their name which gives access to the internal slice data which in turn allows the viewing of  Applications and Geo-Locations.
57 #Creating a user name requires a command of the following format: -
58 #'put', 'users/USER_NAME', {'description':description}
59 #When creating a user name it is usual to add a description and since a user attribute set does not normally contain the users plan name it is best to encode it into the description field.
60
61 sub _export_insert {
62   my ($self, $svc_broadband) = @_;
63   my $rateplan_name = $svc_broadband->{Hash}->{description};
64    $rateplan_name =~ s/\s/_/g;
65
66
67   # load needed info from our end
68   my $cust_main = $svc_broadband->cust_main;
69   return "Could not load service customer" unless $cust_main;
70   my $conf = new FS::Conf;
71
72   # get policy list
73   my $policies = $self->api_get_policies();
74
75   # check for existing rate plan
76   my $existing_rateplan;
77   $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
78
79   # if no existing rate plan create one and modify it.
80   $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
81   $self->api_modify_rateplan($policies->{collection}, $svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
82
83   # set rateplan to existing one or newly created one.
84   my $rateplan = $existing_rateplan ? $existing_rateplan : $self->api_get_rateplan($rateplan_name);
85
86   my @email = map { $_->emailaddress } FS::Record::qsearch({
87         'table'     => 'cust_contact',
88         'select'    => 'emailaddress',
89         'addl_from' => ' JOIN contact_email USING (contactnum)',
90         'hashref'   => { 'custnum' => $cust_main->{Hash}->{custnum}, },
91     });
92   my $username = $email[0];
93   my $description = $cust_main->{Hash}->{first}." ".$cust_main->{Hash}->{last};
94
95   if (!$username) {
96     $self->{'__saisei_error'} = 'no username - can not export';
97     warn "No email found $username\n" if $self->option('debug');
98     return;
99   }
100   else {
101     # check for existing user.
102     my $existing_user;
103     $existing_user = $self->api_get_user($username) unless $self->{'__saisei_error'};
104  
105     # if no existing user create one.
106     $self->api_create_user($username, $description) unless $existing_user;
107
108     # set user to existing one or newly created one.
109     my $user = $existing_user ? $existing_user : $self->api_get_user($username);
110
111     ## add access point ?
112  
113     ## tie host to user
114     $self->api_add_host_to_user($user->{collection}->[0]->{name}, $rateplan->{collection}->[0]->{name}, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
115   }
116
117   return '';
118
119 }
120
121 sub _export_replace {
122   my ($self, $svc_phone) = @_;
123   return '';
124 }
125
126 sub _export_delete {
127   my ($self, $svc_broadband) = @_;
128
129   my $cust_main = $svc_broadband->cust_main;
130   return "Could not load service customer" unless $cust_main;
131   my $conf = new FS::Conf;
132
133   my $rateplan_name = $svc_broadband->{Hash}->{description};
134   $rateplan_name =~ s/\s/_/g;
135
136   my @email = map { $_->emailaddress } FS::Record::qsearch({
137         'table'     => 'cust_contact',
138         'select'    => 'emailaddress',
139         'addl_from' => ' JOIN contact_email USING (contactnum)',
140         'hashref'   => { 'custnum' => $cust_main->{Hash}->{custnum}, },
141     });
142   my $username = $email[0]; 
143
144   ## tie host to user
145   $self->api_delete_host_to_user($username, $rateplan_name, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
146
147   return '';
148 }
149
150 sub _export_suspend {
151   my ($self, $svc_phone) = @_;
152   return '';
153 }
154
155 sub _export_unsuspend {
156   my ($self, $svc_phone) = @_;
157   return '';
158 }
159
160 =head1 Saisei API
161
162 These methods allow access to the Saisei API using the credentials
163 set in the export options.
164
165 =cut
166
167 =head2 api_call
168
169 Accepts I<$service>, I<$method>, I<$params> hashref and optional
170 I<$returnfield>.  Places an api call to the specified service
171 and method with the specified params.  Returns the decoded json
172 object returned by the api call.  If I<$returnfield> is specified,
173 returns only that field of the decoded object, and errors out if
174 that field does not exist.  Returns empty on failure;  retrieve
175 error messages using L</api_error>.
176
177 Must run L</api_login> first.
178
179 =cut
180
181 sub api_call {
182   my ($self,$method,$path,$params) = @_;
183   $self->{'__saisei_error'} = '';
184   my $auth_info = $self->option('username') . ':' . $self->option('password');
185   $params ||= {};
186
187   warn "Calling $method on http://"
188     .$self->{Hash}->{machine}.':'.$self->option('port')
189     ."/rest/stm/configurations/running/$path\n" if $self->option('debug');
190
191   my $data = encode_json($params) if keys %{ $params };
192
193   my $client = REST::Client->new();
194   $client->addHeader("Authorization", "Basic ".encode_base64($auth_info));
195   $client->setHost('http://'.$self->{Hash}->{machine}.':'.$self->option('port'));
196   $client->$method('/rest/stm/configurations/running/'.$path, $data, { "Content-type" => 'application/json'});
197
198   warn "Response Code is ".$client->responseCode()."\n" if $self->option('debug');
199
200   my $result;
201
202   if ($client->responseCode() eq '200' || $client->responseCode() eq '201') {
203     eval { $result = decode_json($client->responseContent()) };
204     unless ($result) {
205       $self->{'__saisei_error'} = "Error decoding json: $@";
206       return;
207     }
208   }
209   else {
210     $self->{'__saisei_error'} = "Bad response from server during $method: " . $client->responseContent();
211     warn "Response Content is\n".$client->responseContent."\n" if $self->option('debug');
212     return; 
213   }
214
215   return $result;
216   
217 }
218
219 =head2 api_error
220
221 Returns the error string set by L</PortaOne API> methods,
222 or a blank string if most recent call produced no errors.
223
224 =cut
225
226 sub api_error {
227   my $self = shift;
228   return $self->{'__saisei_error'} || '';
229 }
230
231 =head2 api_get_policies
232
233 Gets a list of global policies.
234
235 =cut
236
237 sub api_get_policies {
238   my $self = shift;
239
240   my $get_policies = $self->api_call("GET", 'policies/?token=1&order=name&start=0&limit=20&select=name%2Cpercent_rate%2Cassured%2C');
241   return if $self->api_error;
242   $self->{'__saisei_error'} = "Did not receive any global policies"
243     unless $get_policies;
244
245   return $get_policies;
246 }
247
248 =head2 api_get_rateplan
249
250 Gets rateplan info for specific rateplan.
251
252 =cut
253
254 sub api_get_rateplan {
255   my $self = shift;
256   my $rateplan = shift;
257
258   my $get_rateplan = $self->api_call("GET", "rate_plans/$rateplan");
259   return if $self->api_error;
260   $self->{'__saisei_error'} = "Did not receive any rateplan info"
261     unless $get_rateplan;
262
263   return $get_rateplan;
264 }
265
266 =head2 api_get_user
267
268 Gets user info for specific user.
269
270 =cut
271
272 sub api_get_user {
273   my $self = shift;
274   my $user = shift;
275
276   my $get_user = $self->api_call("GET", "users/$user");
277   return if $self->api_error;
278   $self->{'__saisei_error'} = "Did not receive any user info"
279     unless $get_user;
280
281   return $get_user;
282 }
283
284 =head2 api_get_accesspoint
285
286 Gets user info for specific access point.
287
288 =cut
289
290 sub api_get_accesspoint {
291   my $self = shift;
292   my $accesspoint;
293
294   my $get_accesspoint = $self->api_call("GET", "access_points/$accesspoint");
295   return if $self->api_error;
296   $self->{'__saisei_error'} = "Did not receive any user info"
297     unless $get_accesspoint;
298
299   return;
300 }
301
302 =head2 api_create_rateplan
303
304 Creates a rateplan.
305
306 =cut
307
308 sub api_create_rateplan {
309   my ($self, $svc, $rateplan) = @_;
310
311   my $new_rateplan = $self->api_call(
312       "PUT", 
313       "rate_plans/$rateplan",
314       {
315         'downstream_rate' => $svc->{Hash}->{speed_down},
316         'upstream_rate' => $svc->{Hash}->{speed_up},
317       },
318   );
319
320   $self->{'__saisei_error'} = "Rate Plan not created"
321     unless $new_rateplan; # should never happen
322   return $new_rateplan;
323
324 }
325
326 =head2 api_modify_rateplan
327
328 Modify a rateplan.
329
330 =cut
331
332 sub api_modify_rateplan {
333   my ($self,$policies,$svc,$rateplan_name) = @_;
334
335   foreach my $policy (@$policies) {
336     my $policyname = $policy->{name};
337     my $rate_multiplier = '';
338     if ($policy->{background}) { $rate_multiplier = ".01"; }
339     my $modified_rateplan = $self->api_call(
340       "PUT", 
341       "rate_plans/$rateplan_name/partitions/$policyname",
342       {
343         'restricted'      =>  $policy->{assured},         # policy_assured_flag
344         'rate_multiplier' => $rate_multiplier,           # policy_background 0.1
345         'rate'            =>  $policy->{percent_rate}, # policy_percent_rate
346       },
347     );
348
349     $self->{'__saisei_error'} = "Rate Plan not modified"
350       unless $modified_rateplan; # should never happen
351     
352   }
353
354   return;
355  
356 }
357
358 =head2 api_create_user
359
360 Creates a rateplan.
361
362 =cut
363
364 sub api_create_user {
365   my ($self,$user, $description) = @_;
366
367   my $new_user = $self->api_call(
368       "PUT", 
369       "users/$user",
370       {
371         'description' => $description,
372       },
373   );
374
375   $self->{'__saisei_error'} = "User not created"
376     unless $new_user; # should never happen
377
378   return $new_user;
379
380 }
381
382 =head2 api_create_accesspoint
383
384 Creates a access point.
385
386 =cut
387
388 sub api_create_accesspoint {
389   my ($self,$accesspoint) = @_;
390
391   #my $new_accesspoint = $self->api_call(
392   #    "PUT", 
393   #    "access_points/$accesspoint",
394   #    {
395   #      'description' => 'my description',
396   #    },
397   #);
398
399   #$self->{'__saisei_error'} = "Access point not created"
400   #  unless $new_accesspoint; # should never happen
401   return;
402
403 }
404
405 =head2 api_add_host_to_user
406
407 ties host to user and rateplan.
408
409 =cut
410
411 sub api_add_host_to_user {
412   my ($self,$user, $rateplan, $ip) = @_;
413
414   my $new_host = $self->api_call(
415       "PUT", 
416       "hosts/$ip",
417       {
418         'user'      => $user,
419         'rate_plan' => $rateplan,
420       },
421   );
422
423   $self->{'__saisei_error'} = "Host not created"
424     unless $new_host; # should never happen
425
426   return $new_host;
427
428 }
429
430 =head2 api_add_host_to_user
431
432 ties host to user and rateplan.
433
434 =cut
435
436 sub api_delete_host_to_user {
437   my ($self,$user, $rateplan, $ip) = @_;
438
439   my $delete_host = $self->api_call("DELETE", "hosts/$ip");
440
441   $self->{'__saisei_error'} = "Host not created"
442     unless $delete_host; # should never happen
443
444   return $delete_host;
445
446 }
447
448 =head1 SEE ALSO
449
450 L<FS::part_export>
451
452 =cut
453
454 1;