Commit 85c505ba authored by Robert Speicher's avatar Robert Speicher

Merge branch '208832-seat-link-service' into 'master'

Resolve "Seat Link Service"

Closes #208832

See merge request gitlab-org/gitlab!26467
parents 982edbcd b04953fd
......@@ -546,6 +546,9 @@ Gitlab.ee do
Settings.cron_jobs['elastic_index_bulk_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['elastic_index_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
Settings.cron_jobs['elastic_index_bulk_cron_worker']['job_class'] ||= 'ElasticIndexBulkCronWorker'
Settings.cron_jobs['sync_seat_link_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['sync_seat_link_worker']['cron'] ||= "#{rand(60)} 0 * * *"
Settings.cron_jobs['sync_seat_link_worker']['job_class'] = 'SyncSeatLinkWorker'
end
#
......
......@@ -232,6 +232,8 @@
- 2
- - service_desk_email_receiver
- 1
- - sync_seat_link_request
- 1
- - system_hook_push
- 1
- - todos_destroyer
......
......@@ -164,6 +164,13 @@
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: cronjob:sync_seat_link
:feature_category: :analysis
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: cronjob:update_all_mirrors
:feature_category: :source_code_management
:has_external_dependencies:
......@@ -563,3 +570,10 @@
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: sync_seat_link_request
:feature_category: :analysis
:has_external_dependencies: true
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
# frozen_string_literal: true
class SyncSeatLinkRequestWorker
include ApplicationWorker
feature_category :analysis
idempotent!
worker_has_external_dependencies!
URI_PATH = '/api/v1/seat_links'
RequestError = Class.new(StandardError)
def perform(date, license_key, max_historical_user_count)
response = Gitlab::HTTP.post(
URI_PATH,
base_uri: EE::SUBSCRIPTIONS_URL,
headers: request_headers,
body: request_body(date, license_key, max_historical_user_count)
)
raise RequestError, request_error_message(response) unless response.success?
end
private
def request_body(date, license_key, max_historical_user_count)
{
date: date,
license_key: license_key,
max_historical_user_count: max_historical_user_count
}.to_json
end
def request_headers
{ 'Content-Type' => 'application/json' }
end
def request_error_message(response)
"Seat Link request failed! Code:#{response.code} Body:#{response.body}"
end
end
# frozen_string_literal: true
class SyncSeatLinkWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
# rubocop:disable Scalability/CronWorkerContext
# This worker does not perform work scoped to a context
include CronjobQueue
# rubocop:enable Scalability/CronWorkerContext
feature_category :analysis
# Retry for up to approximately 17 hours
sidekiq_options retry: 12, dead: false
def perform
return unless should_sync_seats?
SyncSeatLinkRequestWorker.perform_async(
report_date.to_s,
License.current.data,
max_historical_user_count
)
end
private
# Only sync paid licenses from start date until 14 days after expiration
def should_sync_seats?
License.current &&
!License.current.trial? &&
report_date.between?(License.current.starts_at, License.current.expires_at + 14.days)
end
def max_historical_user_count
HistoricalData.max_historical_user_count(
from: License.current.starts_at,
to: report_date
)
end
def report_date
@report_date ||= Time.now.utc.yesterday.to_date
end
end
---
title: Add worker to sync paid seats info daily in the background.
merge_request: 26467
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
describe SyncSeatLinkRequestWorker, type: :worker do
describe '#perform' do
subject do
described_class.new.perform('2020-01-01', '123', 5)
end
let(:seat_link_url) { [EE::SUBSCRIPTIONS_URL, '/api/v1/seat_links'].join }
it 'makes an HTTP POST request with passed params' do
stub_request(:post, seat_link_url).to_return(status: 200)
subject
expect(WebMock).to have_requested(:post, seat_link_url).with(
headers: { 'Content-Type' => 'application/json' },
body: {
date: '2020-01-01',
license_key: '123',
max_historical_user_count: 5
}.to_json
)
end
context 'when the request is not successful' do
before do
stub_request(:post, seat_link_url)
.to_return(status: 400, body: '{"success":false,"error":"Bad Request"}')
end
it 'raises an error with the expected message' do
expect { subject }.to raise_error(
described_class::RequestError,
'Seat Link request failed! Code:400 Body:{"success":false,"error":"Bad Request"}'
)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe SyncSeatLinkWorker, type: :worker do
describe '#perform' do
def create_current_license(options = {})
License.current.destroy!
gl_license = create(:gitlab_license, options)
create(:license, data: gl_license.export)
end
context 'when current, paid license is active' do
let(:utc_time) { Time.utc(2020, 3, 12, 12, 00) }
before do
# Setting the date as 12th March 2020 12:00 UTC for tests and creating new license
create_current_license(starts_at: '2020-02-12'.to_date)
HistoricalData.create!(date: '2020-02-11'.to_date, active_user_count: 100)
HistoricalData.create!(date: '2020-02-12'.to_date, active_user_count: 10)
HistoricalData.create!(date: '2020-02-13'.to_date, active_user_count: 15)
HistoricalData.create!(date: '2020-03-12'.to_date, active_user_count: 20)
HistoricalData.create!(date: '2020-03-15'.to_date, active_user_count: 25)
allow(SyncSeatLinkRequestWorker).to receive(:perform_async).and_return(true)
end
it 'executes the SyncSeatLinkRequestWorker with expected params' do
Timecop.travel(utc_time) do
subject.perform
expect(SyncSeatLinkRequestWorker).to have_received(:perform_async)
.with(
'2020-03-11',
License.current.data,
15
)
end
end
context 'when the timezone makes date one day in advance' do
before do
Time.zone = 'Auckland'
end
it 'executes the SyncSeatLinkRequestWorker with expected params' do
Timecop.travel(utc_time) do
expect(Date.current.to_s).to eql('2020-03-13')
subject.perform
expect(SyncSeatLinkRequestWorker).to have_received(:perform_async)
.with(
'2020-03-11',
License.current.data,
15
)
end
end
context 'when the timezone makes date one day before than UTC' do
before do
Time.zone = 'Central America'
end
it 'executes the SyncSeatLinkRequestWorker with expected params' do
Timecop.travel(utc_time.beginning_of_day) do
expect(Date.current.to_s).to eql('2020-03-11')
subject.perform
expect(SyncSeatLinkRequestWorker).to have_received(:perform_async)
.with(
'2020-03-11',
License.current.data,
15
)
end
end
end
end
end
shared_examples 'no seat link sync' do
it 'does not execute the SyncSeatLinkRequestWorker' do
expect(SyncSeatLinkRequestWorker).not_to receive(:perform_async)
subject.perform
end
end
context 'when license is missing' do
before do
License.current.destroy!
end
include_examples 'no seat link sync'
end
context 'when using a trial license' do
before do
create(:license, trial: true)
end
include_examples 'no seat link sync'
end
context 'when using an expired license' do
before do
create_current_license(expires_at: expiration_date)
end
context 'the license expired over 15 days ago' do
let(:expiration_date) { Time.now.utc.to_date - 16.days }
include_examples 'no seat link sync'
end
context 'the license expired less than or equal to 15 days ago' do
let(:expiration_date) { Time.now.utc.to_date - 15.days }
it 'executes the SyncSeatLinkRequestWorker' do
expect(SyncSeatLinkRequestWorker).to receive(:perform_async).and_return(true)
subject.perform
end
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