Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests/api/graphql/ci/runner_spec.rb')
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb350
1 files changed, 248 insertions, 102 deletions
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index 986e3ce9e52..52b548ce8b9 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -6,11 +6,13 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
include GraphqlHelpers
let_it_be(:user) { create(:user, :admin) }
- let_it_be(:group) { create(:group) }
+ let_it_be(:another_admin) { create(:user, :admin) }
+ let_it_be_with_reload(:group) { create(:group) }
let_it_be(:active_instance_runner) do
- create(:ci_runner, :instance,
+ create(:ci_runner, :instance, :with_runner_manager,
description: 'Runner 1',
+ creator: user,
contacted_at: 2.hours.ago,
active: true,
version: 'adfe156',
@@ -28,6 +30,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
let_it_be(:inactive_instance_runner) do
create(:ci_runner, :instance,
description: 'Runner 2',
+ creator: another_admin,
contacted_at: 1.day.ago,
active: false,
version: 'adfe157',
@@ -55,7 +58,9 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
end
let_it_be(:project1) { create(:project) }
- let_it_be(:active_project_runner) { create(:ci_runner, :project, projects: [project1]) }
+ let_it_be(:active_project_runner) do
+ create(:ci_runner, :project, :with_runner_manager, projects: [project1])
+ end
shared_examples 'runner details fetch' do
let(:query) do
@@ -77,6 +82,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
expect(runner_data).to match a_graphql_entity_for(
runner,
description: runner.description,
+ created_by: runner.creator ? a_graphql_entity_for(runner.creator) : nil,
created_at: runner.created_at&.iso8601,
contacted_at: runner.contacted_at&.iso8601,
version: runner.version,
@@ -85,7 +91,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
locked: false,
active: runner.active,
paused: !runner.active,
- status: runner.status('14.5').to_s.upcase,
+ status: runner.status.to_s.upcase,
job_execution_status: runner.builds.running.any? ? 'RUNNING' : 'IDLE',
maximum_timeout: runner.maximum_timeout,
access_level: runner.access_level.to_s.upcase,
@@ -107,15 +113,39 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
),
project_count: nil,
admin_url: "http://localhost/admin/runners/#{runner.id}",
+ edit_admin_url: "http://localhost/admin/runners/#{runner.id}/edit",
+ register_admin_url: runner.registration_available? ? "http://localhost/admin/runners/#{runner.id}/register" : nil,
user_permissions: {
'readRunner' => true,
'updateRunner' => true,
'deleteRunner' => true,
'assignRunner' => true
- }
+ },
+ managers: a_hash_including(
+ "count" => runner.runner_managers.count,
+ "nodes" => an_instance_of(Array),
+ "pageInfo" => anything
+ )
)
expect(runner_data['tagList']).to match_array runner.tag_list
end
+
+ it 'does not execute more queries per runner', :use_sql_query_cache, :aggregate_failures do
+ # warm-up license cache and so on:
+ personal_access_token = create(:personal_access_token, user: user)
+ args = { current_user: user, token: { personal_access_token: personal_access_token } }
+ post_graphql(query, **args)
+ expect(graphql_data_at(:runner)).not_to be_nil
+
+ personal_access_token = create(:personal_access_token, user: another_admin)
+ args = { current_user: another_admin, token: { personal_access_token: personal_access_token } }
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { post_graphql(query, **args) }
+
+ create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: another_admin)
+ create(:ci_runner, :project, version: '14.0.1', projects: [project1], tag_list: %w[tag3 tag8], creator: another_admin)
+
+ expect { post_graphql(query, **args) }.not_to exceed_all_query_limit(control)
+ end
end
shared_examples 'retrieval with no admin url' do
@@ -135,7 +165,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
runner_data = graphql_data_at(:runner)
expect(runner_data).not_to be_nil
- expect(runner_data).to match a_graphql_entity_for(runner, admin_url: nil)
+ expect(runner_data).to match a_graphql_entity_for(runner, admin_url: nil, edit_admin_url: nil)
expect(runner_data['tagList']).to match_array runner.tag_list
end
end
@@ -307,6 +337,24 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
it_behaves_like 'runner details fetch'
end
+ describe 'for registration type' do
+ context 'when registered with registration token' do
+ let(:runner) do
+ create(:ci_runner, registration_type: :registration_token)
+ end
+
+ it_behaves_like 'runner details fetch'
+ end
+
+ context 'when registered with authenticated user' do
+ let(:runner) do
+ create(:ci_runner, registration_type: :authenticated_user)
+ end
+
+ it_behaves_like 'runner details fetch'
+ end
+ end
+
describe 'for group runner request' do
let(:query) do
%(
@@ -330,24 +378,110 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
end
end
- describe 'for runner with status' do
- let_it_be(:stale_runner) { create(:ci_runner, description: 'Stale runner 1', created_at: 3.months.ago) }
- let_it_be(:never_contacted_instance_runner) { create(:ci_runner, description: 'Missing runner 1', created_at: 1.month.ago, contacted_at: nil) }
-
- let(:status_fragment) do
+ describe 'ephemeralRegisterUrl' do
+ let(:runner_args) { { registration_type: :authenticated_user, creator: creator } }
+ let(:query) do
%(
- status
- legacyStatusWithExplicitVersion: status(legacyMode: "14.5")
- newStatus: status(legacyMode: null)
+ query {
+ runner(id: "#{runner.to_global_id}") {
+ ephemeralRegisterUrl
+ }
+ }
)
end
+ shared_examples 'has register url' do
+ it 'retrieves register url' do
+ post_graphql(query, current_user: user)
+ expect(graphql_data_at(:runner, :ephemeral_register_url)).to eq(expected_url)
+ end
+ end
+
+ shared_examples 'has no register url' do
+ it 'retrieves no register url' do
+ post_graphql(query, current_user: user)
+ expect(graphql_data_at(:runner, :ephemeral_register_url)).to eq(nil)
+ end
+ end
+
+ context 'with an instance runner', :freeze_time do
+ let(:creator) { user }
+ let(:runner) { create(:ci_runner, **runner_args) }
+
+ context 'with valid ephemeral registration' do
+ it_behaves_like 'has register url' do
+ let(:expected_url) { "http://localhost/admin/runners/#{runner.id}/register" }
+ end
+ end
+
+ context 'when runner ephemeral registration has expired' do
+ let(:runner) do
+ create(:ci_runner, created_at: (Ci::Runner::REGISTRATION_AVAILABILITY_TIME + 1.second).ago, **runner_args)
+ end
+
+ it_behaves_like 'has no register url'
+ end
+
+ context 'when runner has already been registered' do
+ let(:runner) { create(:ci_runner, :with_runner_manager, **runner_args) }
+
+ it_behaves_like 'has no register url'
+ end
+ end
+
+ context 'with a group runner' do
+ let(:creator) { user }
+ let(:runner) { create(:ci_runner, :group, groups: [group], **runner_args) }
+
+ context 'with valid ephemeral registration' do
+ it_behaves_like 'has register url' do
+ let(:expected_url) { "http://localhost/groups/#{group.path}/-/runners/#{runner.id}/register" }
+ end
+ end
+
+ context 'when request not from creator' do
+ let(:creator) { another_admin }
+
+ before do
+ group.add_owner(another_admin)
+ end
+
+ it_behaves_like 'has no register url'
+ end
+ end
+
+ context 'with a project runner' do
+ let(:creator) { user }
+ let(:runner) { create(:ci_runner, :project, projects: [project1], **runner_args) }
+
+ context 'with valid ephemeral registration' do
+ it_behaves_like 'has register url' do
+ let(:expected_url) { "http://localhost/#{project1.full_path}/-/runners/#{runner.id}/register" }
+ end
+ end
+
+ context 'when request not from creator' do
+ let(:creator) { another_admin }
+
+ before do
+ project1.add_owner(another_admin)
+ end
+
+ it_behaves_like 'has no register url'
+ end
+ end
+ end
+
+ describe 'for runner with status' do
+ let_it_be(:stale_runner) { create(:ci_runner, description: 'Stale runner 1', created_at: 3.months.ago) }
+ let_it_be(:never_contacted_instance_runner) { create(:ci_runner, description: 'Missing runner 1', created_at: 1.month.ago, contacted_at: nil) }
+
let(:query) do
%(
query {
- staleRunner: runner(id: "#{stale_runner.to_global_id}") { #{status_fragment} }
- pausedRunner: runner(id: "#{inactive_instance_runner.to_global_id}") { #{status_fragment} }
- neverContactedInstanceRunner: runner(id: "#{never_contacted_instance_runner.to_global_id}") { #{status_fragment} }
+ staleRunner: runner(id: "#{stale_runner.to_global_id}") { status }
+ pausedRunner: runner(id: "#{inactive_instance_runner.to_global_id}") { status }
+ neverContactedInstanceRunner: runner(id: "#{never_contacted_instance_runner.to_global_id}") { status }
}
)
end
@@ -357,23 +491,17 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
stale_runner_data = graphql_data_at(:stale_runner)
expect(stale_runner_data).to match a_hash_including(
- 'status' => 'STALE',
- 'legacyStatusWithExplicitVersion' => 'STALE',
- 'newStatus' => 'STALE'
+ 'status' => 'STALE'
)
paused_runner_data = graphql_data_at(:paused_runner)
expect(paused_runner_data).to match a_hash_including(
- 'status' => 'PAUSED',
- 'legacyStatusWithExplicitVersion' => 'PAUSED',
- 'newStatus' => 'OFFLINE'
+ 'status' => 'OFFLINE'
)
never_contacted_instance_runner_data = graphql_data_at(:never_contacted_instance_runner)
expect(never_contacted_instance_runner_data).to match a_hash_including(
- 'status' => 'NEVER_CONTACTED',
- 'legacyStatusWithExplicitVersion' => 'NEVER_CONTACTED',
- 'newStatus' => 'NEVER_CONTACTED'
+ 'status' => 'NEVER_CONTACTED'
)
end
end
@@ -568,34 +696,34 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
end
end
- context 'with request made by creator' do
+ context 'with request made by creator', :frozen_time do
let(:user) { creator }
context 'with runner created in UI' do
let(:registration_type) { :authenticated_user }
- context 'with runner created in last 3 hours' do
- let(:created_at) { (3.hours - 1.second).ago }
+ context 'with runner created in last hour' do
+ let(:created_at) { (Ci::Runner::REGISTRATION_AVAILABILITY_TIME - 1.second).ago }
- context 'with no runner machine registed yet' do
+ context 'with no runner manager registered yet' do
it_behaves_like 'an ephemeral_authentication_token'
end
- context 'with first runner machine already registed' do
- let!(:runner_machine) { create(:ci_runner_machine, runner: runner) }
+ context 'with first runner manager already registered' do
+ let!(:runner_manager) { create(:ci_runner_machine, runner: runner) }
it_behaves_like 'a protected ephemeral_authentication_token'
end
end
context 'with runner created almost too long ago' do
- let(:created_at) { (3.hours - 1.second).ago }
+ let(:created_at) { (Ci::Runner::REGISTRATION_AVAILABILITY_TIME - 1.second).ago }
it_behaves_like 'an ephemeral_authentication_token'
end
context 'with runner created too long ago' do
- let(:created_at) { 3.hours.ago }
+ let(:created_at) { Ci::Runner::REGISTRATION_AVAILABILITY_TIME.ago }
it_behaves_like 'a protected ephemeral_authentication_token'
end
@@ -604,8 +732,8 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
context 'with runner registered from command line' do
let(:registration_type) { :registration_token }
- context 'with runner created in last 3 hours' do
- let(:created_at) { (3.hours - 1.second).ago }
+ context 'with runner created in last 1 hour' do
+ let(:created_at) { (Ci::Runner::REGISTRATION_AVAILABILITY_TIME - 1.second).ago }
it_behaves_like 'a protected ephemeral_authentication_token'
end
@@ -628,6 +756,12 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
<<~SINGLE
runner(id: "#{runner.to_global_id}") {
#{all_graphql_fields_for('CiRunner', excluded: excluded_fields)}
+ createdBy {
+ id
+ username
+ webPath
+ webUrl
+ }
groups {
nodes {
id
@@ -658,7 +792,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
let(:active_group_runner2) { create(:ci_runner, :group) }
# Exclude fields that are already hardcoded above
- let(:excluded_fields) { %w[jobs groups projects ownerProject] }
+ let(:excluded_fields) { %w[createdBy jobs groups projects ownerProject] }
let(:single_query) do
<<~QUERY
@@ -691,6 +825,8 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
control = ActiveRecord::QueryRecorder.new { post_graphql(single_query, **args) }
+ personal_access_token = create(:personal_access_token, user: another_admin)
+ args = { current_user: another_admin, token: { personal_access_token: personal_access_token } }
expect { post_graphql(double_query, **args) }.not_to exceed_query_limit(control)
expect(graphql_data.count).to eq 6
@@ -721,20 +857,20 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
end
describe 'Query limits with jobs' do
- let!(:group1) { create(:group) }
- let!(:group2) { create(:group) }
- let!(:project1) { create(:project, :repository, group: group1) }
- let!(:project2) { create(:project, :repository, group: group1) }
- let!(:project3) { create(:project, :repository, group: group2) }
+ let_it_be(:group1) { create(:group) }
+ let_it_be(:group2) { create(:group) }
+ let_it_be(:project1) { create(:project, :repository, group: group1) }
+ let_it_be(:project2) { create(:project, :repository, group: group1) }
+ let_it_be(:project3) { create(:project, :repository, group: group2) }
- let!(:merge_request1) { create(:merge_request, source_project: project1) }
- let!(:merge_request2) { create(:merge_request, source_project: project3) }
+ let_it_be(:merge_request1) { create(:merge_request, source_project: project1) }
+ let_it_be(:merge_request2) { create(:merge_request, source_project: project3) }
let(:project_runner2) { create(:ci_runner, :project, projects: [project1, project2]) }
let!(:build1) { create(:ci_build, :success, name: 'Build One', runner: project_runner2, pipeline: pipeline1) }
- let!(:pipeline1) do
+ let_it_be(:pipeline1) do
create(:ci_pipeline, project: project1, source: :merge_request_event, merge_request: merge_request1, ref: 'main',
- target_sha: 'xxx')
+ target_sha: 'xxx')
end
let(:query) do
@@ -745,24 +881,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
jobs {
nodes {
id
- detailedStatus {
- id
- detailsPath
- group
- icon
- text
- }
- project {
- id
- name
- webUrl
- }
- shortSha
- commitPath
- finishedAt
- duration
- queuedDuration
- tags
+ #{field}
}
}
}
@@ -770,42 +889,69 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
QUERY
end
- it 'does not execute more queries per job', :aggregate_failures do
- # warm-up license cache and so on:
- personal_access_token = create(:personal_access_token, user: user)
- args = { current_user: user, token: { personal_access_token: personal_access_token } }
- post_graphql(query, **args)
-
- control = ActiveRecord::QueryRecorder.new(query_recorder_debug: true) { post_graphql(query, **args) }
-
- # Add a new build to project_runner2
- project_runner2.runner_projects << build(:ci_runner_project, runner: project_runner2, project: project3)
- pipeline2 = create(:ci_pipeline, project: project3, source: :merge_request_event, merge_request: merge_request2,
- ref: 'main', target_sha: 'xxx')
- build2 = create(:ci_build, :success, name: 'Build Two', runner: project_runner2, pipeline: pipeline2)
+ context 'when requesting individual fields' do
+ using RSpec::Parameterized::TableSyntax
- args[:current_user] = create(:user, :admin) # do not reuse same user
- expect { post_graphql(query, **args) }.not_to exceed_all_query_limit(control)
+ where(:field) do
+ [
+ 'detailedStatus { id detailsPath group icon text }',
+ 'project { id name webUrl }'
+ ] + %w[
+ shortSha
+ browseArtifactsPath
+ commitPath
+ playPath
+ refPath
+ webPath
+ finishedAt
+ duration
+ queuedDuration
+ tags
+ ]
+ end
- expect(graphql_data.count).to eq 1
- expect(graphql_data).to match(
- a_hash_including(
- 'runner' => a_graphql_entity_for(
- project_runner2,
- jobs: { 'nodes' => containing_exactly(a_graphql_entity_for(build1), a_graphql_entity_for(build2)) }
- )
- ))
+ with_them do
+ it 'does not execute more queries per job', :use_sql_query_cache, :aggregate_failures do
+ admin2 = create(:user, :admin) # do not reuse same user
+
+ # warm-up license cache and so on:
+ personal_access_token = create(:personal_access_token, user: user)
+ personal_access_token2 = create(:personal_access_token, user: admin2)
+ args = { current_user: user, token: { personal_access_token: personal_access_token } }
+ args2 = { current_user: admin2, token: { personal_access_token: personal_access_token2 } }
+ post_graphql(query, **args2)
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { post_graphql(query, **args) }
+
+ # Add a new build to project_runner2
+ project_runner2.runner_projects << build(:ci_runner_project, runner: project_runner2, project: project3)
+ pipeline2 = create(:ci_pipeline, project: project3, source: :merge_request_event, merge_request: merge_request2,
+ ref: 'main', target_sha: 'xxx')
+ build2 = create(:ci_build, :success, name: 'Build Two', runner: project_runner2, pipeline: pipeline2)
+
+ expect { post_graphql(query, **args2) }.not_to exceed_all_query_limit(control)
+
+ expect(graphql_data.count).to eq 1
+ expect(graphql_data).to match(
+ a_hash_including(
+ 'runner' => a_graphql_entity_for(
+ project_runner2,
+ jobs: { 'nodes' => containing_exactly(a_graphql_entity_for(build1), a_graphql_entity_for(build2)) }
+ )
+ ))
+ end
+ end
end
end
describe 'sorting and pagination' do
let(:query) do
<<~GQL
- query($id: CiRunnerID!, $projectSearchTerm: String, $n: Int, $cursor: String) {
- runner(id: $id) {
- #{fields}
+ query($id: CiRunnerID!, $projectSearchTerm: String, $n: Int, $cursor: String) {
+ runner(id: $id) {
+ #{fields}
+ }
}
- }
GQL
end
@@ -824,18 +970,18 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
let(:fields) do
<<~QUERY
- projects(search: $projectSearchTerm, first: $n, after: $cursor) {
- count
- nodes {
- id
- }
- pageInfo {
- hasPreviousPage
- startCursor
- endCursor
- hasNextPage
+ projects(search: $projectSearchTerm, first: $n, after: $cursor) {
+ count
+ nodes {
+ id
+ }
+ pageInfo {
+ hasPreviousPage
+ startCursor
+ endCursor
+ hasNextPage
+ }
}
- }
QUERY
end