v3: throttle and enable request creation

This commit is contained in:
Konstantin Haase 2015-03-23 15:18:36 +01:00
parent 1d1e1a4733
commit 03938cabdc
8 changed files with 69 additions and 27 deletions

View File

@ -50,7 +50,7 @@ GIT
GIT GIT
remote: git://github.com/travis-ci/travis-core.git remote: git://github.com/travis-ci/travis-core.git
revision: e71a198112cbb6abefbbf0c2a096414ee9853a24 revision: 9ef018a267e94251dbaac54503a410baca71d1c6
specs: specs:
travis-core (0.0.1) travis-core (0.0.1)
actionmailer (~> 3.2.19) actionmailer (~> 3.2.19)

View File

@ -24,16 +24,17 @@ module Travis
load_dir("#{__dir__}/v3/extensions") load_dir("#{__dir__}/v3/extensions")
load_dir("#{__dir__}/v3") load_dir("#{__dir__}/v3")
ClientError = Error .create(status: 400) ClientError = Error .create(status: 400)
NotFound = ClientError .create(:resource, status: 404, template: '%s not found (or insufficient access)') NotFound = ClientError .create(:resource, status: 404, template: '%s not found (or insufficient access)')
EntityMissing = NotFound .create(type: 'not_found') EntityMissing = NotFound .create(type: 'not_found')
WrongCredentials = ClientError .create('access denied', status: 403) WrongCredentials = ClientError .create('access denied', status: 403)
LoginRequired = ClientError .create('login required', status: 403) LoginRequired = ClientError .create('login required', status: 403)
InsufficientAccess = ClientError .create(status: 403) InsufficientAccess = ClientError .create(status: 403)
PushAccessRequired = InsufficientAccess .create('push access required') PushAccessRequired = InsufficientAccess .create('push access required')
WrongParams = ClientError .create('wrong parameters') WrongParams = ClientError .create('wrong parameters')
ServerError = Error .create(status: 500) ServerError = Error .create(status: 500)
NotImplemented = ServerError .create('request not (yet) implemented', status: 501) NotImplemented = ServerError .create('request not (yet) implemented', status: 501)
RequestLimitReached = ClientError .create('request limit reached for resource', status: 429)
end end
end end
end end

View File

@ -6,13 +6,16 @@ module Travis::API::V3
raise ServerError, 'repository does not have a github_id'.freeze unless repository.github_id raise ServerError, 'repository does not have a github_id'.freeze unless repository.github_id
raise WrongParams, 'missing user'.freeze unless user and user.id raise WrongParams, 'missing user'.freeze unless user and user.id
perform_async(:build_request, type: 'api'.freeze, credentials: {}, payload: { payload = {
repository: { id: repository.github_id }, repository: { id: repository.github_id },
user: { id: user.id }, user: { id: user.id },
message: message, message: message,
branch: branch || repository.default_branch_name, branch: branch || repository.default_branch_name,
config: config || {} config: config || {}
}) }
perform_async(:build_request, type: 'api'.freeze, credentials: {}, payload: payload)
payload
end end
end end
end end

View File

@ -1,6 +1,13 @@
module Travis::API::V3 module Travis::API::V3
class Queries::Requests < Query class Queries::Requests < Query
def find(repository) def find(repository)
repository.requests
end
def count(repository, time_frame)
find(repository).
where(event_type: 'api'.freeze, result: 'accepted'.freeze).
where('created_at > ?'.freeze, time_frame.ago).count
end end
end end
end end

View File

@ -2,11 +2,8 @@ module Travis::API::V3
module Renderer::Accepted module Renderer::Accepted
extend self extend self
def render(type, **) def render(payload, **options)
{ { :@type => 'pending'.freeze, **Renderer.render_value(payload) }
:@type => 'pending'.freeze,
:resource_type => type
}
end end
end end
end end

View File

@ -52,8 +52,9 @@ module Travis::API::V3
params.keys.any? { |key| key.start_with? "#{prefix}." } params.keys.any? { |key| key.start_with? "#{prefix}." }
end end
def accepted(type = self.class.result_type) def accepted(**payload)
Result.new(:accepted, type, status: 202) payload[:resource_type] ||= self.class.result_type
Result.new(:accepted, payload, status: 202)
end end
def not_implemented def not_implemented

View File

@ -1,5 +1,9 @@
module Travis::API::V3 module Travis::API::V3
class Services::Requests::Create < Service class Services::Requests::Create < Service
TIME_FRAME = 1.hour
LIMIT = 10
private_constant :TIME_FRAME, :LIMIT
result_type :request result_type :request
def run def run
@ -7,13 +11,20 @@ module Travis::API::V3
raise NotFound unless repository = find(:repository) raise NotFound unless repository = find(:repository)
raise PushAccessRequired, repository: repository unless access_control.writable?(repository) raise PushAccessRequired, repository: repository unless access_control.writable?(repository)
user = find(:user) if access_control.full_access? and params_for? 'user'.freeze user = find(:user) if access_control.full_access? and params_for? 'user'.freeze
user ||= access_control.user user ||= access_control.user
remaining = remaining_requests(repository)
not_implemented unless Travis::Features.owner_active?(:request_create, repository.owner) raise RequestLimitReached, repository: repository if remaining == 0
query.schedule(repository, user) payload = query.schedule(repository, user)
accepted(:request) accepted(remaining_requests: remaining, repository: repository, request: payload)
end
def remaining_requests(repository)
return LIMIT if access_control.full_access?
count = query(:requests).count(repository, TIME_FRAME)
count > LIMIT ? 0 : LIMIT - count
end end
end end
end end

View File

@ -3,6 +3,7 @@ require 'spec_helper'
describe Travis::API::V3::Services::Requests::Create do describe Travis::API::V3::Services::Requests::Create do
let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first } let(:repo) { Travis::API::V3::Models::Repository.where(owner_name: 'svenfuchs', name: 'minimal').first }
let(:sidekiq_payload) { Sidekiq::Client.last['args'].last[:payload] } let(:sidekiq_payload) { Sidekiq::Client.last['args'].last[:payload] }
before { repo.requests.each(&:delete) }
before do before do
Travis::Features.stubs(:owner_active?).returns(true) Travis::Features.stubs(:owner_active?).returns(true)
@ -83,8 +84,16 @@ describe Travis::API::V3::Services::Requests::Create do
example { expect(last_response.status).to be == 202 } example { expect(last_response.status).to be == 202 }
example { expect(JSON.load(body)).to be == { example { expect(JSON.load(body)).to be == {
"@type" => "pending", "@type" => "pending",
"resource_type" => "request" "remaining_requests" => 10,
"repository" => {"@type"=>"repository", "@href"=>"/repo/#{repo.id}", "id"=>repo.id, "slug"=>"svenfuchs/minimal"},
"request" => {
"repository" => {"id"=>repo.id},
"user" => {"id"=>repo.owner.id},
"message" => nil,
"branch" => "master",
"config" => {}},
"resource_type" => "request"
}} }}
example { expect(sidekiq_payload).to be == { example { expect(sidekiq_payload).to be == {
@ -207,6 +216,19 @@ describe Travis::API::V3::Services::Requests::Create do
config: {} config: {}
}} }}
end end
describe "when request limit is reached" do
before { 10.times { repo.requests.create(event_type: 'api', result: 'accepted') } }
before { post("/v3/repo/#{repo.id}/requests", params, headers) }
example { expect(last_response.status).to be == 429 }
example { expect(JSON.load(body)).to be == {
"@type" => "error",
"error_type" => "request_limit_reached",
"error_message" => "request limit reached for resource",
"repository" => {"@type"=>"repository", "@href"=>"/repo/#{repo.id}", "id"=>repo.id, "slug"=>"svenfuchs/minimal" }
}}
end
end end