Show profile data in log if config.profiling_enabled. refs #1815
[arvados.git] / apps / workbench / app / models / arvados_api_client.rb
1 require 'httpclient'
2 require 'thread'
3
4 class ArvadosApiClient
5   class NotLoggedInException < StandardError
6   end
7   class InvalidApiResponseException < StandardError
8   end
9
10   @@client_mtx = Mutex.new
11   @@api_client = nil
12   @@profiling_enabled = Rails.configuration.profiling_enabled rescue false
13
14   def api(resources_kind, action, data=nil)
15     profile_checkpoint
16
17     @@client_mtx.synchronize do
18       if not @@api_client 
19         @@api_client = HTTPClient.new
20         if Rails.configuration.arvados_insecure_https
21           @@api_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
22         end
23       end
24     end
25
26     api_token = Thread.current[:arvados_api_token]
27     api_token ||= ''
28
29     resources_kind = class_kind(resources_kind).pluralize if resources_kind.is_a? Class
30     url = "#{self.arvados_v1_base}/#{resources_kind}#{action}"
31
32     query = {"api_token" => api_token}
33     if !data.nil?
34       data.each do |k,v|
35         if v.is_a? String or v.nil?
36           query[k] = v
37         elsif v == true
38           query[k] = 1
39         elsif v == false
40           query[k] = 0
41         else
42           query[k] = JSON.dump(v)
43         end
44       end
45     else
46       query["_method"] = "GET"
47     end
48     if @@profiling_enabled
49       query["_profile"] = "true"
50     end
51     
52     header = {"Accept" => "application/json"}
53
54     profile_checkpoint { "Prepare request #{url}" }
55     msg = @@api_client.post(url, 
56                             query,
57                             header: header)
58     profile_checkpoint 'API transaction'
59
60     if msg.status_code == 401
61       raise NotLoggedInException.new
62     end
63
64     json = msg.content
65     
66     begin
67       resp = Oj.load(json, :symbol_keys => true)
68     rescue Oj::ParseError
69       raise InvalidApiResponseException.new json
70     end
71     if not resp.is_a? Hash
72       raise InvalidApiResponseException.new json
73     end
74     if msg.status_code != 200
75       errors = resp[:errors]
76       errors = errors.join("\n\n") if errors.is_a? Array
77       raise "API error #{msg.status_code}:\n\n#{errors}\n"
78     end
79     if resp[:_profile]
80       Rails.logger.info "API client: " \
81       "#{resp.delete(:_profile)[:request_time]} request_time"
82     end
83     profile_checkpoint 'Parse response'
84     resp
85   end
86
87   def unpack_api_response(j, kind=nil)
88     if j.is_a? Hash and j[:items].is_a? Array and j[:kind].match(/(_list|List)$/)
89       ary = j[:items].collect { |x| unpack_api_response x, j[:kind] }
90       if j[:items_available]
91         (class << ary; self; end).class_eval { attr_accessor :items_available }
92         ary.items_available = j[:items_available]
93       end
94       ary
95     elsif j.is_a? Hash and (kind || j[:kind])
96       oclass = self.kind_class(kind || j[:kind])
97       if oclass
98         j.keys.each do |k|
99           childkind = j["#{k.to_s}_kind".to_sym]
100           if childkind
101             j[k] = self.unpack_api_response(j[k], childkind)
102           end
103         end
104         oclass.new.private_reload(j)
105       else
106         j
107       end
108     else
109       j
110     end
111   end
112
113   def arvados_login_url(params={})
114     if Rails.configuration.respond_to? :arvados_login_base
115       uri = Rails.configuration.arvados_login_base
116     else
117       uri = self.arvados_v1_base.sub(%r{/arvados/v\d+.*}, '/login')
118     end
119     if params.size > 0
120       uri += '?' << params.collect { |k,v|
121         CGI.escape(k.to_s) + '=' + CGI.escape(v.to_s)
122       }.join('&')
123     end
124   end
125
126   def arvados_logout_url(params={})
127     arvados_login_url(params).sub('/login','/logout')
128   end
129
130   def arvados_v1_base
131     Rails.configuration.arvados_v1_base
132   end
133
134   def arvados_schema
135     @arvados_schema ||= api 'schema', ''
136   end
137
138   def kind_class(kind)
139     kind.match(/^arvados\#(.+?)(_list|List)?$/)[1].pluralize.classify.constantize rescue nil
140   end
141
142   def class_kind(resource_class)
143     resource_class.to_s.underscore
144   end
145
146   protected
147   def profile_checkpoint label=nil
148     label = yield if block_given?
149     t = Time.now
150     if label and @profile_t0
151       Rails.logger.info "API client: #{t - @profile_t0} #{label}"
152     end
153     @profile_t0 = t
154   end
155 end