Commit 4b0465f2 authored by Stan Hu's avatar Stan Hu

Address review comments with playing pipeline scheduler

parent 02e7e499
class Projects::PipelineSchedulesController < Projects::ApplicationController class Projects::PipelineSchedulesController < Projects::ApplicationController
before_action :schedule, except: [:index, :new, :create] before_action :schedule, except: [:index, :new, :create]
before_action :play_rate_limit, only: [:play]
before_action :authorize_play_pipeline_schedule!, only: [:play] before_action :authorize_play_pipeline_schedule!, only: [:play]
before_action :authorize_read_pipeline_schedule! before_action :authorize_read_pipeline_schedule!
before_action :authorize_create_pipeline_schedule!, only: [:new, :create] before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
...@@ -42,21 +43,13 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController ...@@ -42,21 +43,13 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
end end
def play def play
limiter = ::Gitlab::ActionRateLimiter.new(action: :play_pipeline_schedule)
if limiter.throttled?(throttle_key, 1)
flash[:alert] = 'You cannot play this scheduled pipeline at the moment. Please wait a minute.'
return redirect_to pipeline_schedules_path(@project)
end
job_id = RunPipelineScheduleWorker.perform_async(schedule.id, current_user.id) job_id = RunPipelineScheduleWorker.perform_async(schedule.id, current_user.id)
flash[:notice] = if job_id
if job_id flash[:notice] = "Successfully scheduled a pipeline to run. Go to the <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details.".html_safe
"Successfully scheduled a pipeline to run. Go to the <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details.".html_safe else
else flash[:alert] = 'Unable to schedule a pipeline to run immediately'
'Unable to schedule a pipeline to run immediately' end
end
redirect_to pipeline_schedules_path(@project) redirect_to pipeline_schedules_path(@project)
end end
...@@ -81,8 +74,15 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController ...@@ -81,8 +74,15 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
private private
def throttle_key def play_rate_limit
"user:#{current_user.id}:schedule:#{schedule.id}" return unless current_user
limiter = ::Gitlab::ActionRateLimiter.new(action: :play_pipeline_schedule)
return unless limiter.throttled?([current_user, schedule], 1)
flash[:alert] = 'You cannot play this scheduled pipeline at the moment. Please wait a minute.'
redirect_to pipeline_schedules_path(@project)
end end
def schedule def schedule
......
...@@ -12,11 +12,15 @@ module Gitlab ...@@ -12,11 +12,15 @@ module Gitlab
@expiry_time = expiry_time @expiry_time = expiry_time
end end
# Increments the given cache key and increments the value by 1 with the
# given expiration time. Returns the incremented value.
#
# key - An array of ActiveRecord instances
def increment(key) def increment(key)
value = 0 value = 0
Gitlab::Redis::Cache.with do |redis| Gitlab::Redis::Cache.with do |redis|
cache_key = "action_rate_limiter:#{action}:#{key}" cache_key = action_key(key)
value = redis.incr(cache_key) value = redis.incr(cache_key)
redis.expire(cache_key, expiry_time) if value == 1 redis.expire(cache_key, expiry_time) if value == 1
end end
...@@ -24,8 +28,20 @@ module Gitlab ...@@ -24,8 +28,20 @@ module Gitlab
value value
end end
# Increments the given key and returns true if the action should
# be throttled.
#
# key - An array of ActiveRecord instances
# threshold_value - The maximum number of times this action should occur in the given time interval
def throttled?(key, threshold_value) def throttled?(key, threshold_value)
self.increment(key) > threshold_value self.increment(key) > threshold_value
end end
private
def action_key(key)
serialized = key.map { |obj| "#{obj.class.model_name.to_s.underscore}:#{obj.id}" }.join(":")
"action_rate_limiter:#{action}:#{serialized}"
end
end end
end end
...@@ -370,13 +370,27 @@ describe Projects::PipelineSchedulesController do ...@@ -370,13 +370,27 @@ describe Projects::PipelineSchedulesController do
set(:user) { create(:user) } set(:user) { create(:user) }
let(:ref) { 'master' } let(:ref) { 'master' }
context 'when a developer makes the request' do before do
project.add_developer(user)
sign_in(user)
end
context 'when an anonymous user makes the request' do
before do before do
project.add_developer(user) sign_out(user)
end
sign_in(user) it 'does not allow pipeline to be executed' do
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
expect(response).to have_gitlab_http_status(404)
end end
end
context 'when a developer makes the request' do
it 'executes a new pipeline' do it 'executes a new pipeline' do
expect(RunPipelineScheduleWorker).to receive(:perform_async).with(pipeline_schedule.id, user.id).and_return('job-123') expect(RunPipelineScheduleWorker).to receive(:perform_async).with(pipeline_schedule.id, user.id).and_return('job-123')
......
...@@ -2,8 +2,10 @@ require 'spec_helper' ...@@ -2,8 +2,10 @@ require 'spec_helper'
describe Gitlab::ActionRateLimiter do describe Gitlab::ActionRateLimiter do
let(:redis) { double('redis') } let(:redis) { double('redis') }
let(:key) { 'user:1' } let(:user) { create(:user) }
let(:cache_key) { "action_rate_limiter:test_action:#{key}" } let(:project) { create(:project) }
let(:key) { [user, project] }
let(:cache_key) { "action_rate_limiter:test_action:user:#{user.id}:project:#{project.id}" }
subject { described_class.new(action: :test_action, expiry_time: 100) } subject { described_class.new(action: :test_action, expiry_time: 100) }
......
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