5 class NotLoggedInException < StandardError
7 class InvalidApiResponseException < StandardError
10 @@client_mtx = Mutex.new
12 @@profiling_enabled = Rails.configuration.profiling_enabled
14 def api(resources_kind, action, data=nil)
17 @@client_mtx.synchronize do
19 @@api_client = HTTPClient.new
20 if Rails.configuration.arvados_insecure_https
21 @@api_client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
23 # Use system CA certificates
24 @@api_client.ssl_config.add_trust_ca('/etc/ssl/certs')
29 resources_kind = class_kind(resources_kind).pluralize if resources_kind.is_a? Class
30 url = "#{self.arvados_v1_base}/#{resources_kind}#{action}"
32 # Clean up /arvados/v1/../../discovery/v1 to /discovery/v1
33 url.sub! '/arvados/v1/../../', '/'
36 'api_token' => Thread.current[:arvados_api_token] || '',
37 'reader_tokens' => (Thread.current[:reader_tokens] || []).to_json,
41 if v.is_a? String or v.nil?
48 query[k] = JSON.dump(v)
52 query["_method"] = "GET"
54 if @@profiling_enabled
55 query["_profile"] = "true"
58 header = {"Accept" => "application/json"}
60 profile_checkpoint { "Prepare request #{url} #{query[:uuid]} #{query[:where]} #{query[:filters]}" }
61 msg = @@api_client.post(url,
64 profile_checkpoint 'API transaction'
66 if msg.status_code == 401
67 raise NotLoggedInException.new
73 resp = Oj.load(json, :symbol_keys => true)
75 raise InvalidApiResponseException.new json
77 if not resp.is_a? Hash
78 raise InvalidApiResponseException.new json
80 if msg.status_code != 200
81 errors = resp[:errors]
82 errors = errors.join("\n\n") if errors.is_a? Array
83 raise "#{errors} [API: #{msg.status_code}]"
86 Rails.logger.info "API client: " \
87 "#{resp.delete(:_profile)[:request_time]} request_time"
89 profile_checkpoint 'Parse response'
93 def self.patch_paging_vars(ary, items_available, offset, limit, links=nil)
95 (class << ary; self; end).class_eval { attr_accessor :items_available }
96 ary.items_available = items_available
99 (class << ary; self; end).class_eval { attr_accessor :offset }
103 (class << ary; self; end).class_eval { attr_accessor :limit }
107 (class << ary; self; end).class_eval { attr_accessor :links }
113 def unpack_api_response(j, kind=nil)
114 if j.is_a? Hash and j[:items].is_a? Array and j[:kind].match(/(_list|List)$/)
115 ary = j[:items].collect { |x| unpack_api_response x, x[:kind] }
116 links = ArvadosResourceList.new Link
117 links.results = (j[:links] || []).collect do |x|
118 unpack_api_response x, x[:kind]
120 self.class.patch_paging_vars(ary, j[:items_available], j[:offset], j[:limit], links)
121 elsif j.is_a? Hash and (kind || j[:kind])
122 oclass = self.kind_class(kind || j[:kind])
125 childkind = j["#{k.to_s}_kind".to_sym]
127 j[k] = self.unpack_api_response(j[k], childkind)
130 oclass.new.private_reload(j)
139 def arvados_login_url(params={})
140 if Rails.configuration.respond_to? :arvados_login_base
141 uri = Rails.configuration.arvados_login_base
143 uri = self.arvados_v1_base.sub(%r{/arvados/v\d+.*}, '/login')
146 uri += '?' << params.collect { |k,v|
147 CGI.escape(k.to_s) + '=' + CGI.escape(v.to_s)
152 def arvados_logout_url(params={})
153 arvados_login_url(params).sub('/login','/logout')
157 Rails.configuration.arvados_v1_base
161 @discovery ||= api '../../discovery/v1/apis/arvados/v1/rest', ''
165 kind.match(/^arvados\#(.+?)(_list|List)?$/)[1].pluralize.classify.constantize rescue nil
168 def class_kind(resource_class)
169 resource_class.to_s.underscore
173 def profile_checkpoint label=nil
174 return if !@@profiling_enabled
175 label = yield if block_given?
177 if label and @profile_t0
178 Rails.logger.info "API client: #{t - @profile_t0} #{label}"