Bug #15527
closedapi/v2/hosts is slow to load permissions for non-admins
Description
After http://projects.theforeman.org/issues/13639 , the show response of api/v2/hosts (used in GET/PUT/POST/DELETE requests) checks if certain permissions are true for a pair 'user/host'.
The response looks like
"permissions": { "view_hosts": true, "create_hosts": true, "edit_hosts": true, "destroy_hosts": true, "build_hosts": true, "power_hosts": true, "console_hosts": true, "ipmi_boot": true, "puppetrun_hosts": true, "view_discovered_hosts": true, "provision_discovered_hosts": true, "edit_discovered_hosts": true, "destroy_discovered_hosts": true, "submit_discovered_hosts": true, "auto_provision_discovered_hosts": true }
To do this, it calls `.can?` on all permissions: https://github.com/theforeman/foreman/blob/develop/app/views/api/v2/hosts/show.json.rabl#L38
`.can?` finds all Host::Managed to which a permission can be applied to, then calls `.include?(host)` on the collection to see whether the permission action is applicable to the host in question: https://github.com/theforeman/foreman/blob/develop/app/services/authorizer.rb#L15
This means any API call will have to call
find_collectionat least 9 times (more depending on plugins).
find_collectionis an expensive query to make, for an user that has a lot of permissions it can take seconds.
irb(main):129:0> Permission.where(:resource_type => "Host").all.inject({}) do |hash, permission| irb(main):130:1* puts permission.name irb(main):131:1> puts Benchmark.measure { irb(main):132:2* authorizer.can?(permission.name, host) irb(main):133:2> } irb(main):134:1> end view_hosts 1.560000 0.220000 1.780000 ( 1.878100) create_hosts 2.270000 0.190000 2.460000 ( 2.562798) edit_hosts 1.700000 0.170000 1.870000 ( 1.972199) destroy_hosts 1.650000 0.210000 1.860000 ( 2.002876) build_hosts 2.150000 0.180000 2.330000 ( 2.452150) power_hosts 1.630000 0.200000 1.830000 ( 1.928230) console_hosts 1.610000 0.200000 1.810000 ( 1.913544) ipmi_boot 1.650000 0.210000 1.860000 ( 1.957224) puppetrun_hosts 2.610000 0.200000 2.810000 ( 2.913967) => nil
find_collection is also considerably slower when the user has a lot of roles. For example `.can?` calls on an user with 15 roles took ~2 seconds, while calls on an user with 7 roles took ~0.5 seconds. This is in an environment without organizations and locations.
We had reports on #theforeman of requests that took 10 seconds to run on this endpoint. Thanks to Steve Traylen & Nacho Barrientos from CERN for their help debugging this issue on IRC.
One quick "fix" we should implement is to start using the Rails cache in Authorizer, so that it survives multiple requests, unlike the current cache in memory. Obviously we should also make find_collection faster, maybe making the 'WHERE' query at the same time we call find_collection would make it faster.