Commit 9543025e authored by Adam Niedzielski's avatar Adam Niedzielski

Introduce "polling_interval_multiplier" as application setting

Implement module for setting "Poll-Interval" response header.
Return 429 in ETag caching middleware when polling is disabled.
parent 2faf955c
...@@ -134,6 +134,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -134,6 +134,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:unique_ips_limit_enabled, :unique_ips_limit_enabled,
:version_check_enabled, :version_check_enabled,
:terminal_max_session_time, :terminal_max_session_time,
:polling_interval_multiplier,
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
import_sources: [], import_sources: [],
......
...@@ -131,6 +131,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -131,6 +131,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 } numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :polling_interval_multiplier,
presence: true,
numericality: { greater_than_or_equal_to: 0 }
validates_each :restricted_visibility_levels do |record, attr, value| validates_each :restricted_visibility_levels do |record, attr, value|
value&.each do |level| value&.each do |level|
unless Gitlab::VisibilityLevel.options.has_value?(level) unless Gitlab::VisibilityLevel.options.has_value?(level)
...@@ -233,7 +237,8 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -233,7 +237,8 @@ class ApplicationSetting < ActiveRecord::Base
signup_enabled: Settings.gitlab['signup_enabled'], signup_enabled: Settings.gitlab['signup_enabled'],
terminal_max_session_time: 0, terminal_max_session_time: 0,
two_factor_grace_period: 48, two_factor_grace_period: 48,
user_default_external: false user_default_external: false,
polling_interval_multiplier: 1
} }
end end
......
...@@ -558,5 +558,19 @@ ...@@ -558,5 +558,19 @@
Maximum time for web terminal websocket connection (in seconds). Maximum time for web terminal websocket connection (in seconds).
0 for unlimited. 0 for unlimited.
%fieldset
%legend Real-time features
.form-group
= f.label :polling_interval_multiplier, 'Polling interval multiplier', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :polling_interval_multiplier, class: 'form-control'
.help-block
Change this value to influence how frequently the GitLab UI polls for updates.
If you set the value to 2 all polling intervals are multiplied
by 2, which means that polling happens half as frequently.
The multiplier can also have a decimal value.
The default value (1) is a reasonable choice for the majority of GitLab
installations. Set to 0 to completely disable polling.
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
---
title: Introduce "polling_interval_multiplier" as application setting
merge_request: 10280
author:
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddPollingIntervalMultiplierToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
disable_ddl_transaction!
def up
add_column_with_default :application_settings, :polling_interval_multiplier, :decimal, default: 1, allow_null: false
end
def down
remove_column :application_settings, :polling_interval_multiplier
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170317203554) do ActiveRecord::Schema.define(version: 20170329124448) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -115,6 +115,7 @@ ActiveRecord::Schema.define(version: 20170317203554) do ...@@ -115,6 +115,7 @@ ActiveRecord::Schema.define(version: 20170317203554) do
t.integer "unique_ips_limit_per_user" t.integer "unique_ips_limit_per_user"
t.integer "unique_ips_limit_time_window" t.integer "unique_ips_limit_time_window"
t.boolean "unique_ips_limit_enabled", default: false, null: false t.boolean "unique_ips_limit_enabled", default: false, null: false
t.decimal "polling_interval_multiplier", default: 1.0, null: false
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
......
...@@ -48,7 +48,8 @@ Example response: ...@@ -48,7 +48,8 @@ Example response:
"koding_url": null, "koding_url": null,
"plantuml_enabled": false, "plantuml_enabled": false,
"plantuml_url": null, "plantuml_url": null,
"terminal_max_session_time": 0 "terminal_max_session_time": 0,
"polling_interval_multiplier": 1.0
} }
``` ```
...@@ -88,6 +89,7 @@ PUT /application/settings ...@@ -88,6 +89,7 @@ PUT /application/settings
| `plantuml_enabled` | boolean | no | Enable PlantUML integration. Default is `false`. | | `plantuml_enabled` | boolean | no | Enable PlantUML integration. Default is `false`. |
| `plantuml_url` | string | yes (if `plantuml_enabled` is `true`) | The PlantUML instance URL for integration. | | `plantuml_url` | string | yes (if `plantuml_enabled` is `true`) | The PlantUML instance URL for integration. |
| `terminal_max_session_time` | integer | no | Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time. | | `terminal_max_session_time` | integer | no | Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time. |
| `polling_interval_multiplier` | decimal | no | Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling. |
```bash ```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal
...@@ -124,6 +126,7 @@ Example response: ...@@ -124,6 +126,7 @@ Example response:
"koding_url": null, "koding_url": null,
"plantuml_enabled": false, "plantuml_enabled": false,
"plantuml_url": null, "plantuml_url": null,
"terminal_max_session_time": 0 "terminal_max_session_time": 0,
"polling_interval_multiplier": 1.0
} }
``` ```
...@@ -581,6 +581,7 @@ module API ...@@ -581,6 +581,7 @@ module API
expose :plantuml_enabled expose :plantuml_enabled
expose :plantuml_url expose :plantuml_url
expose :terminal_max_session_time expose :terminal_max_session_time
expose :polling_interval_multiplier
end end
class Release < Grape::Entity class Release < Grape::Entity
......
...@@ -110,6 +110,7 @@ module API ...@@ -110,6 +110,7 @@ module API
requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run." requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run."
end end
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility, at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility,
:default_group_visibility, :restricted_visibility_levels, :import_sources, :default_group_visibility, :restricted_visibility_levels, :import_sources,
:enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit, :enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit,
...@@ -125,7 +126,7 @@ module API ...@@ -125,7 +126,7 @@ module API
:akismet_enabled, :admin_notification_email, :sentry_enabled, :akismet_enabled, :admin_notification_email, :sentry_enabled,
:repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled, :repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled,
:version_check_enabled, :email_author_in_body, :html_emails_enabled, :version_check_enabled, :email_author_in_body, :html_emails_enabled,
:housekeeping_enabled, :terminal_max_session_time :housekeeping_enabled, :terminal_max_session_time, :polling_interval_multiplier
end end
put "application/settings" do put "application/settings" do
attrs = declared_params(include_missing: false) attrs = declared_params(include_missing: false)
......
...@@ -18,8 +18,7 @@ module Gitlab ...@@ -18,8 +18,7 @@ module Gitlab
if_none_match = env['HTTP_IF_NONE_MATCH'] if_none_match = env['HTTP_IF_NONE_MATCH']
if if_none_match == etag if if_none_match == etag
Gitlab::Metrics.add_event(:etag_caching_cache_hit) handle_cache_hit(etag)
[304, { 'ETag' => etag }, ['']]
else else
track_cache_miss(if_none_match, cached_value_present) track_cache_miss(if_none_match, cached_value_present)
...@@ -52,6 +51,14 @@ module Gitlab ...@@ -52,6 +51,14 @@ module Gitlab
%Q{W/"#{value}"} %Q{W/"#{value}"}
end end
def handle_cache_hit(etag)
Gitlab::Metrics.add_event(:etag_caching_cache_hit)
status_code = Gitlab::PollingInterval.polling_enabled? ? 304 : 429
[status_code, { 'ETag' => etag }, ['']]
end
def track_cache_miss(if_none_match, cached_value_present) def track_cache_miss(if_none_match, cached_value_present)
if if_none_match.blank? if if_none_match.blank?
Gitlab::Metrics.add_event(:etag_caching_header_missing) Gitlab::Metrics.add_event(:etag_caching_header_missing)
......
module Gitlab
class PollingInterval
include Gitlab::CurrentSettings
HEADER_NAME = 'Poll-Interval'.freeze
def self.set_header(response, interval:)
if polling_enabled?
multiplier = current_application_settings.polling_interval_multiplier
value = (interval * multiplier).to_i
else
value = -1
end
response.headers[HEADER_NAME] = value
end
def self.polling_enabled?
!current_application_settings.polling_interval_multiplier.zero?
end
end
end
...@@ -99,6 +99,19 @@ describe Gitlab::EtagCaching::Middleware do ...@@ -99,6 +99,19 @@ describe Gitlab::EtagCaching::Middleware do
middleware.call(build_env(path, if_none_match)) middleware.call(build_env(path, if_none_match))
end end
context 'when polling is disabled' do
before do
allow(Gitlab::PollingInterval).to receive(:polling_enabled?).
and_return(false)
end
it 'returns status code 429' do
status, _, _ = middleware.call(build_env(path, if_none_match))
expect(status).to eq 429
end
end
end end
context 'when If-None-Match header does not match ETag in store' do context 'when If-None-Match header does not match ETag in store' do
......
require 'spec_helper'
describe Gitlab::PollingInterval, lib: true do
let(:polling_interval) { described_class }
describe '.set_header' do
let(:headers) { {} }
let(:response) { double(headers: headers) }
context 'when polling is disabled' do
before do
stub_application_setting(polling_interval_multiplier: 0)
end
it 'sets value to -1' do
polling_interval.set_header(response, interval: 10_000)
expect(headers['Poll-Interval']).to eq(-1)
end
end
context 'when polling is enabled' do
before do
stub_application_setting(polling_interval_multiplier: 0.33333)
end
it 'applies modifier to base interval' do
polling_interval.set_header(response, interval: 10_000)
expect(headers['Poll-Interval']).to eq(3333)
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment