2023-03-17 16:20:25 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
RSpec.describe Ci::RunnerManager, feature_category: :runner_fleet, type: :model do
|
2023-03-17 16:20:25 +05:30
|
|
|
it_behaves_like 'having unique enum values'
|
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
it_behaves_like 'it has loose foreign keys' do
|
|
|
|
let(:factory_name) { :ci_runner_machine }
|
|
|
|
end
|
|
|
|
|
2023-03-17 16:20:25 +05:30
|
|
|
it { is_expected.to belong_to(:runner) }
|
2023-04-23 21:23:45 +05:30
|
|
|
it { is_expected.to belong_to(:runner_version).with_foreign_key(:version) }
|
2023-06-20 00:43:36 +05:30
|
|
|
it { is_expected.to have_many(:runner_manager_builds) }
|
|
|
|
it { is_expected.to have_many(:builds).through(:runner_manager_builds) }
|
2023-03-17 16:20:25 +05:30
|
|
|
|
|
|
|
describe 'validation' do
|
|
|
|
it { is_expected.to validate_presence_of(:runner) }
|
2023-04-23 21:23:45 +05:30
|
|
|
it { is_expected.to validate_presence_of(:system_xid) }
|
|
|
|
it { is_expected.to validate_length_of(:system_xid).is_at_most(64) }
|
2023-03-17 16:20:25 +05:30
|
|
|
it { is_expected.to validate_length_of(:version).is_at_most(2048) }
|
|
|
|
it { is_expected.to validate_length_of(:revision).is_at_most(255) }
|
|
|
|
it { is_expected.to validate_length_of(:platform).is_at_most(255) }
|
|
|
|
it { is_expected.to validate_length_of(:architecture).is_at_most(255) }
|
|
|
|
it { is_expected.to validate_length_of(:ip_address).is_at_most(1024) }
|
|
|
|
|
|
|
|
context 'when runner has config' do
|
|
|
|
it 'is valid' do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager = build(:ci_runner_machine, config: { gpus: "all" })
|
2023-03-17 16:20:25 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(runner_manager).to be_valid
|
2023-03-17 16:20:25 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when runner has an invalid config' do
|
|
|
|
it 'is invalid' do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager = build(:ci_runner_machine, config: { test: 1 })
|
2023-03-17 16:20:25 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(runner_manager).not_to be_valid
|
2023-03-17 16:20:25 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.stale', :freeze_time do
|
|
|
|
subject { described_class.stale.ids }
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
let!(:runner_manager1) { create(:ci_runner_machine, :stale) }
|
|
|
|
let!(:runner_manager2) { create(:ci_runner_machine, :stale, contacted_at: nil) }
|
|
|
|
let!(:runner_manager3) { create(:ci_runner_machine, created_at: 6.months.ago, contacted_at: Time.current) }
|
|
|
|
let!(:runner_manager4) { create(:ci_runner_machine, created_at: 5.days.ago) }
|
|
|
|
let!(:runner_manager5) do
|
2023-03-17 16:20:25 +05:30
|
|
|
create(:ci_runner_machine, created_at: (7.days - 1.second).ago, contacted_at: (7.days - 1.second).ago)
|
|
|
|
end
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
it 'returns stale runner managers' do
|
|
|
|
is_expected.to match_array([runner_manager1.id, runner_manager2.id])
|
2023-03-17 16:20:25 +05:30
|
|
|
end
|
|
|
|
end
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
describe '.online_contact_time_deadline', :freeze_time do
|
|
|
|
subject { described_class.online_contact_time_deadline }
|
|
|
|
|
|
|
|
it { is_expected.to eq(2.hours.ago) }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.stale_deadline', :freeze_time do
|
|
|
|
subject { described_class.stale_deadline }
|
|
|
|
|
|
|
|
it { is_expected.to eq(7.days.ago) }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#status', :freeze_time do
|
2023-06-20 00:43:36 +05:30
|
|
|
let(:runner_manager) { build(:ci_runner_machine, created_at: 8.days.ago) }
|
2023-05-27 22:25:52 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
subject { runner_manager.status }
|
2023-05-27 22:25:52 +05:30
|
|
|
|
|
|
|
context 'if never connected' do
|
|
|
|
before do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager.contacted_at = nil
|
2023-05-27 22:25:52 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to eq(:stale) }
|
|
|
|
|
|
|
|
context 'if created recently' do
|
|
|
|
before do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager.created_at = 1.day.ago
|
2023-05-27 22:25:52 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to eq(:never_contacted) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'if contacted 1s ago' do
|
|
|
|
before do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager.contacted_at = 1.second.ago
|
2023-05-27 22:25:52 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to eq(:online) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'if contacted recently' do
|
|
|
|
before do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager.contacted_at = 2.hours.ago
|
2023-05-27 22:25:52 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to eq(:offline) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'if contacted long time ago' do
|
|
|
|
before do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager.contacted_at = 7.days.ago
|
2023-05-27 22:25:52 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to eq(:stale) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-04-23 21:23:45 +05:30
|
|
|
describe '#heartbeat', :freeze_time do
|
2023-06-20 00:43:36 +05:30
|
|
|
let(:runner_manager) { create(:ci_runner_machine, version: '15.0.0') }
|
2023-04-23 21:23:45 +05:30
|
|
|
let(:executor) { 'shell' }
|
|
|
|
let(:values) do
|
|
|
|
{
|
|
|
|
ip_address: '8.8.8.8',
|
|
|
|
architecture: '18-bit',
|
|
|
|
config: { gpus: "all" },
|
|
|
|
executor: executor,
|
|
|
|
version: version
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
subject(:heartbeat) do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager.heartbeat(values)
|
2023-04-23 21:23:45 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'when database was updated recently' do
|
|
|
|
before do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager.contacted_at = Time.current
|
2023-04-23 21:23:45 +05:30
|
|
|
end
|
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
context 'when version is changed' do
|
|
|
|
let(:version) { '15.0.1' }
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
before do
|
|
|
|
allow(Ci::Runners::ProcessRunnerVersionUpdateWorker).to receive(:perform_async).with(version)
|
|
|
|
end
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
it 'schedules version information update' do
|
|
|
|
heartbeat
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).to have_received(:perform_async).with(version).once
|
|
|
|
end
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
it 'updates cache' do
|
|
|
|
expect_redis_update
|
|
|
|
|
|
|
|
heartbeat
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(runner_manager.runner_version).to be_nil
|
2023-05-27 22:25:52 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'when fetching runner releases is disabled' do
|
|
|
|
before do
|
|
|
|
stub_application_setting(update_runner_versions_enabled: false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not schedule version information update' do
|
|
|
|
heartbeat
|
|
|
|
|
|
|
|
expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).not_to have_received(:perform_async)
|
|
|
|
end
|
|
|
|
end
|
2023-04-23 21:23:45 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'with only ip_address specified' do
|
|
|
|
let(:values) do
|
|
|
|
{ ip_address: '1.1.1.1' }
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates only ip_address' do
|
2023-05-27 22:25:52 +05:30
|
|
|
expect_redis_update(values.merge(contacted_at: Time.current))
|
|
|
|
|
|
|
|
heartbeat
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with new version having been cached' do
|
|
|
|
let(:version) { '15.0.1' }
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
before do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager.cache_attributes(version: version)
|
2023-04-23 21:23:45 +05:30
|
|
|
end
|
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
it 'does not lose cached version value' do
|
2023-06-20 00:43:36 +05:30
|
|
|
expect { heartbeat }.not_to change { runner_manager.version }.from(version)
|
2023-05-27 22:25:52 +05:30
|
|
|
end
|
2023-04-23 21:23:45 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when database was not updated recently' do
|
|
|
|
before do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager.contacted_at = 2.hours.ago
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
allow(Ci::Runners::ProcessRunnerVersionUpdateWorker).to receive(:perform_async).with(version)
|
2023-04-23 21:23:45 +05:30
|
|
|
end
|
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
context 'when version is changed' do
|
|
|
|
let(:version) { '15.0.1' }
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
context 'with invalid runner_manager' do
|
2023-05-27 22:25:52 +05:30
|
|
|
before do
|
2023-06-20 00:43:36 +05:30
|
|
|
runner_manager.runner = nil
|
2023-05-27 22:25:52 +05:30
|
|
|
end
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
it 'still updates redis cache and database' do
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(runner_manager).to be_invalid
|
2023-05-27 22:25:52 +05:30
|
|
|
|
|
|
|
expect_redis_update
|
|
|
|
does_db_update
|
|
|
|
|
|
|
|
expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).to have_received(:perform_async)
|
|
|
|
.with(version).once
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates redis cache and database' do
|
2023-04-23 21:23:45 +05:30
|
|
|
expect_redis_update
|
|
|
|
does_db_update
|
|
|
|
|
|
|
|
expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).to have_received(:perform_async)
|
|
|
|
.with(version).once
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
context 'with unchanged runner_manager version' do
|
|
|
|
let(:version) { runner_manager.version }
|
2023-04-23 21:23:45 +05:30
|
|
|
|
|
|
|
it 'does not schedule ci_runner_versions update' do
|
|
|
|
heartbeat
|
|
|
|
|
|
|
|
expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).not_to have_received(:perform_async)
|
|
|
|
end
|
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
Ci::Runner::EXECUTOR_NAME_TO_TYPES.each_key do |executor|
|
|
|
|
context "with #{executor} executor" do
|
|
|
|
let(:executor) { executor }
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
it 'updates with expected executor type' do
|
|
|
|
expect_redis_update
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
heartbeat
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(runner_manager.reload.read_attribute(:executor_type)).to eq(expected_executor_type)
|
2023-05-27 22:25:52 +05:30
|
|
|
end
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
def expected_executor_type
|
|
|
|
executor.gsub(/[+-]/, '_')
|
|
|
|
end
|
2023-04-23 21:23:45 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
context 'with an unknown executor type' do
|
|
|
|
let(:executor) { 'some-unknown-type' }
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
it 'updates with unknown executor type' do
|
|
|
|
expect_redis_update
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
heartbeat
|
2023-04-23 21:23:45 +05:30
|
|
|
|
2023-06-20 00:43:36 +05:30
|
|
|
expect(runner_manager.reload.read_attribute(:executor_type)).to eq('unknown')
|
2023-05-27 22:25:52 +05:30
|
|
|
end
|
2023-04-23 21:23:45 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
def expect_redis_update(values = anything)
|
|
|
|
values_json = values == anything ? anything : Gitlab::Json.dump(values)
|
|
|
|
|
2023-04-23 21:23:45 +05:30
|
|
|
Gitlab::Redis::Cache.with do |redis|
|
2023-06-20 00:43:36 +05:30
|
|
|
redis_key = runner_manager.send(:cache_attribute_key)
|
2023-05-27 22:25:52 +05:30
|
|
|
expect(redis).to receive(:set).with(redis_key, values_json, any_args).and_call_original
|
2023-04-23 21:23:45 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def does_db_update
|
2023-06-20 00:43:36 +05:30
|
|
|
expect { heartbeat }.to change { runner_manager.reload.read_attribute(:contacted_at) }
|
|
|
|
.and change { runner_manager.reload.read_attribute(:architecture) }
|
|
|
|
.and change { runner_manager.reload.read_attribute(:config) }
|
|
|
|
.and change { runner_manager.reload.read_attribute(:executor_type) }
|
2023-04-23 21:23:45 +05:30
|
|
|
end
|
|
|
|
end
|
2023-03-17 16:20:25 +05:30
|
|
|
end
|