runner_spec.rb 58.2 KB
Newer Older
1 2
require 'spec_helper'

Shinya Maeda's avatar
Shinya Maeda committed
3
describe API::Runner, :clean_gitlab_redis_shared_state do
4
  include StubGitlabCalls
5
  include RedisHelpers
6 7 8 9

  let(:registration_token) { 'abcdefg123456' }

  before do
Shinya Maeda's avatar
Shinya Maeda committed
10
    stub_feature_flags(ci_enable_live_trace: true)
11 12
    stub_gitlab_calls
    stub_application_setting(runners_registration_token: registration_token)
13
    allow_any_instance_of(Ci::Runner).to receive(:cache_attributes)
14 15 16 17 18 19 20
  end

  describe '/api/v4/runners' do
    describe 'POST /api/v4/runners' do
      context 'when no token is provided' do
        it 'returns 400 error' do
          post api('/runners')
21

22
          expect(response).to have_gitlab_http_status 400
23 24 25 26 27
        end
      end

      context 'when invalid token is provided' do
        it 'returns 403 error' do
blackst0ne's avatar
blackst0ne committed
28
          post api('/runners'), params: { token: 'invalid' }
29

30
          expect(response).to have_gitlab_http_status 403
31 32 33 34 35
        end
      end

      context 'when valid token is provided' do
        it 'creates runner with default values' do
blackst0ne's avatar
blackst0ne committed
36
          post api('/runners'), params: { token: registration_token }
37 38 39

          runner = Ci::Runner.first

40
          expect(response).to have_gitlab_http_status 201
41 42 43
          expect(json_response['id']).to eq(runner.id)
          expect(json_response['token']).to eq(runner.token)
          expect(runner.run_untagged).to be true
44
          expect(runner.active).to be true
45
          expect(runner.token).not_to eq(registration_token)
46
          expect(runner).to be_instance_type
47 48 49
        end

        context 'when project token is used' do
50
          let(:project) { create(:project) }
51

52
          it 'creates project runner' do
blackst0ne's avatar
blackst0ne committed
53
            post api('/runners'), params: { token: project.runners_token }
54

55
            expect(response).to have_gitlab_http_status 201
56
            expect(project.runners.size).to eq(1)
57 58 59 60
            runner = Ci::Runner.first
            expect(runner.token).not_to eq(registration_token)
            expect(runner.token).not_to eq(project.runners_token)
            expect(runner).to be_project_type
61 62
          end
        end
63 64 65 66 67

        context 'when group token is used' do
          let(:group) { create(:group) }

          it 'creates a group runner' do
blackst0ne's avatar
blackst0ne committed
68
            post api('/runners'), params: { token: group.runners_token }
69 70 71

            expect(response).to have_http_status 201
            expect(group.runners.size).to eq(1)
72 73 74 75
            runner = Ci::Runner.first
            expect(runner.token).not_to eq(registration_token)
            expect(runner.token).not_to eq(group.runners_token)
            expect(runner).to be_group_type
76 77 78 79 80 81
          end
        end
      end

      context 'when runner description is provided' do
        it 'creates runner' do
blackst0ne's avatar
blackst0ne committed
82 83 84 85
          post api('/runners'), params: {
                                  token: registration_token,
                                  description: 'server.hostname'
                                }
86

87
          expect(response).to have_gitlab_http_status 201
88 89 90 91 92 93
          expect(Ci::Runner.first.description).to eq('server.hostname')
        end
      end

      context 'when runner tags are provided' do
        it 'creates runner' do
blackst0ne's avatar
blackst0ne committed
94 95 96 97
          post api('/runners'), params: {
                                  token: registration_token,
                                  tag_list: 'tag1, tag2'
                                }
98

99
          expect(response).to have_gitlab_http_status 201
100 101 102 103 104 105 106
          expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
        end
      end

      context 'when option for running untagged jobs is provided' do
        context 'when tags are provided' do
          it 'creates runner' do
blackst0ne's avatar
blackst0ne committed
107 108 109 110 111
            post api('/runners'), params: {
                                    token: registration_token,
                                    run_untagged: false,
                                    tag_list: ['tag']
                                  }
112

113
            expect(response).to have_gitlab_http_status 201
114 115 116 117 118 119
            expect(Ci::Runner.first.run_untagged).to be false
            expect(Ci::Runner.first.tag_list.sort).to eq(['tag'])
          end
        end

        context 'when tags are not provided' do
120
          it 'returns 400 error' do
blackst0ne's avatar
blackst0ne committed
121 122 123 124
            post api('/runners'), params: {
                                    token: registration_token,
                                    run_untagged: false
                                  }
125

126 127 128
            expect(response).to have_gitlab_http_status 400
            expect(json_response['message']).to include(
              'tags_list' => ['can not be empty when runner is not allowed to pick untagged jobs'])
129 130 131 132 133 134
          end
        end
      end

      context 'when option for locking Runner is provided' do
        it 'creates runner' do
blackst0ne's avatar
blackst0ne committed
135 136 137 138
          post api('/runners'), params: {
                                  token: registration_token,
                                  locked: true
                                }
139

140
          expect(response).to have_gitlab_http_status 201
141 142 143 144
          expect(Ci::Runner.first.locked).to be true
        end
      end

145 146 147
      context 'when option for activating a Runner is provided' do
        context 'when active is set to true' do
          it 'creates runner' do
blackst0ne's avatar
blackst0ne committed
148 149 150 151
            post api('/runners'), params: {
                                    token: registration_token,
                                    active: true
                                  }
152 153 154 155 156 157 158 159

            expect(response).to have_gitlab_http_status 201
            expect(Ci::Runner.first.active).to be true
          end
        end

        context 'when active is set to false' do
          it 'creates runner' do
blackst0ne's avatar
blackst0ne committed
160 161 162 163
            post api('/runners'), params: {
                                    token: registration_token,
                                    active: false
                                  }
164 165 166 167 168 169 170

            expect(response).to have_gitlab_http_status 201
            expect(Ci::Runner.first.active).to be false
          end
        end
      end

171
      context 'when maximum job timeout is specified' do
172
        it 'creates runner' do
blackst0ne's avatar
blackst0ne committed
173 174 175 176
          post api('/runners'), params: {
                                  token: registration_token,
                                  maximum_timeout: 9000
                                }
177 178

          expect(response).to have_gitlab_http_status 201
179
          expect(Ci::Runner.first.maximum_timeout).to eq(9000)
180 181 182 183
        end

        context 'when maximum job timeout is empty' do
          it 'creates runner' do
blackst0ne's avatar
blackst0ne committed
184 185 186 187
            post api('/runners'), params: {
                                    token: registration_token,
                                    maximum_timeout: ''
                                  }
188 189

            expect(response).to have_gitlab_http_status 201
190
            expect(Ci::Runner.first.maximum_timeout).to be_nil
191
          end
192 193 194
        end
      end

195 196 197 198
      %w(name version revision platform architecture).each do |param|
        context "when info parameter '#{param}' info is present" do
          let(:value) { "#{param}_value" }

199
          it "updates provided Runner's parameter" do
blackst0ne's avatar
blackst0ne committed
200 201 202 203
            post api('/runners'), params: {
                                    token: registration_token,
                                    info: { param => value }
                                  }
204

205
            expect(response).to have_gitlab_http_status 201
206 207 208 209
            expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
          end
        end
      end
210 211 212

      it "sets the runner's ip_address" do
        post api('/runners'),
213 214
             params: { token: registration_token },
             headers: { 'X-Forwarded-For' => '123.111.123.111' }
215 216 217 218

        expect(response).to have_gitlab_http_status 201
        expect(Ci::Runner.first.ip_address).to eq('123.111.123.111')
      end
219 220 221 222 223 224
    end

    describe 'DELETE /api/v4/runners' do
      context 'when no token is provided' do
        it 'returns 400 error' do
          delete api('/runners')
Robert Schilling's avatar
Robert Schilling committed
225

226
          expect(response).to have_gitlab_http_status 400
227 228 229 230 231
        end
      end

      context 'when invalid token is provided' do
        it 'returns 403 error' do
blackst0ne's avatar
blackst0ne committed
232
          delete api('/runners'), params: { token: 'invalid' }
Robert Schilling's avatar
Robert Schilling committed
233

234
          expect(response).to have_gitlab_http_status 403
235 236 237 238 239 240 241
        end
      end

      context 'when valid token is provided' do
        let(:runner) { create(:ci_runner) }

        it 'deletes Runner' do
blackst0ne's avatar
blackst0ne committed
242
          delete api('/runners'), params: { token: runner.token }
Robert Schilling's avatar
Robert Schilling committed
243

244
          expect(response).to have_gitlab_http_status 204
245 246
          expect(Ci::Runner.count).to eq(0)
        end
247 248 249 250 251

        it_behaves_like '412 response' do
          let(:request) { api('/runners') }
          let(:params) { { token: runner.token } }
        end
252 253
      end
    end
254 255 256 257 258 259 260 261

    describe 'POST /api/v4/runners/verify' do
      let(:runner) { create(:ci_runner) }

      context 'when no token is provided' do
        it 'returns 400 error' do
          post api('/runners/verify')

262
          expect(response).to have_gitlab_http_status :bad_request
263 264 265 266 267
        end
      end

      context 'when invalid token is provided' do
        it 'returns 403 error' do
blackst0ne's avatar
blackst0ne committed
268
          post api('/runners/verify'), params: { token: 'invalid-token' }
269

270
          expect(response).to have_gitlab_http_status 403
271 272 273 274
        end
      end

      context 'when valid token is provided' do
275
        it 'verifies Runner credentials' do
blackst0ne's avatar
blackst0ne committed
276
          post api('/runners/verify'), params: { token: runner.token }
277

278
          expect(response).to have_gitlab_http_status 200
279 280 281
        end
      end
    end
282
  end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
283 284

  describe '/api/v4/jobs' do
285
    let(:project) { create(:project, shared_runners_enabled: false) }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
286
    let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') }
287
    let(:runner) { create(:ci_runner, :project, projects: [project]) }
288
    let(:job) do
Tomasz Maczukin's avatar
Tomasz Maczukin committed
289
      create(:ci_build, :artifacts, :extended_options,
290
             pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
291
    end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
292 293 294 295 296 297

    describe 'POST /api/v4/jobs/request' do
      let!(:last_update) {}
      let!(:new_update) { }
      let(:user_agent) { 'gitlab-runner 9.0.0 (9-0-stable; go1.7.4; linux/amd64)' }

298
      before do
299
        job
300 301
        stub_container_registry_config(enabled: false)
      end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
302 303

      shared_examples 'no jobs available' do
304 305 306
        before do
          request_job
        end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
307 308 309 310

        context 'when runner sends version in User-Agent' do
          context 'for stable version' do
            it 'gives 204 and set X-GitLab-Last-Update' do
311
              expect(response).to have_gitlab_http_status(204)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
312 313 314 315 316 317 318 319
              expect(response.header).to have_key('X-GitLab-Last-Update')
            end
          end

          context 'when last_update is up-to-date' do
            let(:last_update) { runner.ensure_runner_queue_value }

            it 'gives 204 and set the same X-GitLab-Last-Update' do
320
              expect(response).to have_gitlab_http_status(204)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
321 322 323 324 325 326 327 328 329
              expect(response.header['X-GitLab-Last-Update']).to eq(last_update)
            end
          end

          context 'when last_update is outdated' do
            let(:last_update) { runner.ensure_runner_queue_value }
            let(:new_update) { runner.tick_runner_queue }

            it 'gives 204 and set a new X-GitLab-Last-Update' do
330
              expect(response).to have_gitlab_http_status(204)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
331 332 333 334
              expect(response.header['X-GitLab-Last-Update']).to eq(new_update)
            end
          end

335
          context 'when beta version is sent' do
Tomasz Maczukin's avatar
Tomasz Maczukin committed
336
            let(:user_agent) { 'gitlab-runner 9.0.0~beta.167.g2b2bacc (master; go1.7.4; linux/amd64)' }
337

338
            it { expect(response).to have_gitlab_http_status(204) }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
339 340
          end

341
          context 'when pre-9-0 version is sent' do
Tomasz Maczukin's avatar
Tomasz Maczukin committed
342
            let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0 (1-6-stable; go1.6.3; linux/amd64)' }
343

344
            it { expect(response).to have_gitlab_http_status(204) }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
345 346
          end

347
          context 'when pre-9-0 beta version is sent' do
Tomasz Maczukin's avatar
Tomasz Maczukin committed
348
            let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (master; go1.6.3; linux/amd64)' }
349

350
            it { expect(response).to have_gitlab_http_status(204) }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
351 352 353 354 355 356 357
          end
        end
      end

      context 'when no token is provided' do
        it 'returns 400 error' do
          post api('/jobs/request')
358

359
          expect(response).to have_gitlab_http_status 400
Tomasz Maczukin's avatar
Tomasz Maczukin committed
360 361 362 363 364
        end
      end

      context 'when invalid token is provided' do
        it 'returns 403 error' do
blackst0ne's avatar
blackst0ne committed
365
          post api('/jobs/request'), params: { token: 'invalid' }
366

367
          expect(response).to have_gitlab_http_status 403
Tomasz Maczukin's avatar
Tomasz Maczukin committed
368 369 370 371 372 373
        end
      end

      context 'when valid token is provided' do
        context 'when Runner is not active' do
          let(:runner) { create(:ci_runner, :inactive) }
374
          let(:update_value) { runner.ensure_runner_queue_value }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
375

376
          it 'returns 204 error' do
Tomasz Maczukin's avatar
Tomasz Maczukin committed
377
            request_job
378

379 380
            expect(response).to have_gitlab_http_status(204)
            expect(response.header['X-GitLab-Last-Update']).to eq(update_value)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
381 382 383 384
          end
        end

        context 'when jobs are finished' do
385 386 387
          before do
            job.success
          end
388

Tomasz Maczukin's avatar
Tomasz Maczukin committed
389 390 391 392 393 394 395 396 397 398 399 400 401
          it_behaves_like 'no jobs available'
        end

        context 'when other projects have pending jobs' do
          before do
            job.success
            create(:ci_build, :pending)
          end

          it_behaves_like 'no jobs available'
        end

        context 'when shared runner requests job for project without shared_runners_enabled' do
402
          let(:runner) { create(:ci_runner, :instance) }
403

Tomasz Maczukin's avatar
Tomasz Maczukin committed
404 405 406 407
          it_behaves_like 'no jobs available'
        end

        context 'when there is a pending job' do
408 409 410 411 412 413
          let(:expected_job_info) do
            { 'name' => job.name,
              'stage' => job.stage,
              'project_id' => job.project.id,
              'project_name' => job.project.name }
          end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
414

415 416 417 418 419
          let(:expected_git_info) do
            { 'repo_url' => job.repo_url,
              'ref' => job.ref,
              'sha' => job.sha,
              'before_sha' => job.before_sha,
Tomasz Maczukin's avatar
Tomasz Maczukin committed
420
              'ref_type' => 'branch' }
421
          end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
422

423 424
          let(:expected_steps) do
            [{ 'name' => 'script',
425
               'script' => %w(echo),
Tomasz Maczukin's avatar
Tomasz Maczukin committed
426
               'timeout' => job.metadata_timeout,
427 428 429 430
               'when' => 'on_success',
               'allow_failure' => false },
             { 'name' => 'after_script',
               'script' => %w(ls date),
Tomasz Maczukin's avatar
Tomasz Maczukin committed
431
               'timeout' => job.metadata_timeout,
432 433 434
               'when' => 'always',
               'allow_failure' => true }]
          end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
435

436
          let(:expected_variables) do
437 438
            [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true },
             { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true },
439 440
             { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true }]
          end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
441

442
          let(:expected_artifacts) do
443 444 445 446
            [{ 'name' => 'artifacts_file',
               'untracked' => false,
               'paths' => %w(out/),
               'when' => 'always',
447 448 449
               'expire_in' => '7d',
               "artifact_type" => "archive",
               "artifact_format" => "zip" }]
450
          end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
451

452 453 454
          let(:expected_cache) do
            [{ 'key' => 'cache_key',
               'untracked' => false,
455 456
               'paths' => ['vendor/*'],
               'policy' => 'pull-push' }]
457 458
          end

459 460
          let(:expected_features) { { 'trace_sections' => true } }

Tomasz Maczukin's avatar
Tomasz Maczukin committed
461
          it 'picks a job' do
Tomasz Maczukin's avatar
Tomasz Maczukin committed
462
            request_job info: { platform: :darwin }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
463

464
            expect(response).to have_gitlab_http_status(201)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
465 466
            expect(response.headers).not_to have_key('X-GitLab-Last-Update')
            expect(runner.reload.platform).to eq('darwin')
467 468
            expect(json_response['id']).to eq(job.id)
            expect(json_response['token']).to eq(job.token)
469 470
            expect(json_response['job_info']).to eq(expected_job_info)
            expect(json_response['git_info']).to eq(expected_git_info)
471 472 473
            expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh' })
            expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil,
                                                       'alias' => nil, 'command' => nil },
474
                                                     { 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh',
475
                                                       'alias' => 'docker', 'command' => 'sleep 30' }])
476 477
            expect(json_response['steps']).to eq(expected_steps)
            expect(json_response['artifacts']).to eq(expected_artifacts)
478
            expect(json_response['cache']).to eq(expected_cache)
479
            expect(json_response['variables']).to include(*expected_variables)
480
            expect(json_response['features']).to eq(expected_features)
481 482 483
          end

          context 'when job is made for tag' do
484
            let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
485 486 487

            it 'sets branch as ref_type' do
              request_job
488

489
              expect(response).to have_gitlab_http_status(201)
490 491 492 493 494 495 496
              expect(json_response['git_info']['ref_type']).to eq('tag')
            end
          end

          context 'when job is made for branch' do
            it 'sets tag as ref_type' do
              request_job
497

498
              expect(response).to have_gitlab_http_status(201)
499 500
              expect(json_response['git_info']['ref_type']).to eq('branch')
            end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
501 502 503 504 505 506
          end

          it 'updates runner info' do
            expect { request_job }.to change { runner.reload.contacted_at }
          end

507
          %w(version revision platform architecture).each do |param|
Tomasz Maczukin's avatar
Tomasz Maczukin committed
508 509 510
            context "when info parameter '#{param}' is present" do
              let(:value) { "#{param}_value" }

511
              it "updates provided Runner's parameter" do
Tomasz Maczukin's avatar
Tomasz Maczukin committed
512
                request_job info: { param => value }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
513

514
                expect(response).to have_gitlab_http_status(201)
515
                expect(runner.reload.read_attribute(param.to_sym)).to eq(value)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
516 517 518 519
              end
            end
          end

520 521
          it "sets the runner's ip_address" do
            post api('/jobs/request'),
blackst0ne's avatar
blackst0ne committed
522
              params: { token: runner.token },
523
              headers: { 'User-Agent' => user_agent, 'X-Forwarded-For' => '123.222.123.222' }
524 525 526 527 528

            expect(response).to have_gitlab_http_status 201
            expect(runner.reload.ip_address).to eq('123.222.123.222')
          end

529 530 531 532 533 534 535 536 537
          it "handles multiple X-Forwarded-For addresses" do
            post api('/jobs/request'),
              params: { token: runner.token },
              headers: { 'User-Agent' => user_agent, 'X-Forwarded-For' => '123.222.123.222, 127.0.0.1' }

            expect(response).to have_gitlab_http_status 201
            expect(runner.reload.ip_address).to eq('123.222.123.222')
          end

Tomasz Maczukin's avatar
Tomasz Maczukin committed
538 539
          context 'when concurrently updating a job' do
            before do
540 541
              expect_any_instance_of(Ci::Build).to receive(:run!)
                  .and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
Tomasz Maczukin's avatar
Tomasz Maczukin committed
542 543 544 545
            end

            it 'returns a conflict' do
              request_job
546

547
              expect(response).to have_gitlab_http_status(409)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
548 549 550 551 552
              expect(response.headers).not_to have_key('X-GitLab-Last-Update')
            end
          end

          context 'when project and pipeline have multiple jobs' do
553 554
            let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
            let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) }
555
            let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
556

557 558 559 560 561 562 563 564
            before do
              job.success
              job2.success
            end

            it 'returns dependent jobs' do
              request_job

565
              expect(response).to have_gitlab_http_status(201)
566 567
              expect(json_response['id']).to eq(test_job.id)
              expect(json_response['dependencies'].count).to eq(2)
568 569 570 571 572 573 574
              expect(json_response['dependencies']).to include(
                { 'id' => job.id, 'name' => job.name, 'token' => job.token },
                { 'id' => job2.id, 'name' => job2.name, 'token' => job2.token })
            end
          end

          context 'when pipeline have jobs with artifacts' do
575
            let!(:job) { create(:ci_build, :tag, :artifacts, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
576 577 578 579 580 581 582 583 584
            let!(:test_job) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }

            before do
              job.success
            end

            it 'returns dependent jobs' do
              request_job

585
              expect(response).to have_gitlab_http_status(201)
586 587 588 589 590
              expect(json_response['id']).to eq(test_job.id)
              expect(json_response['dependencies'].count).to eq(1)
              expect(json_response['dependencies']).to include(
                { 'id' => job.id, 'name' => job.name, 'token' => job.token,
                  'artifacts_file' => { 'filename' => 'ci_build_artifacts.zip', 'size' => 106365 } })
591 592 593 594
            end
          end

          context 'when explicit dependencies are defined' do
595 596
            let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
            let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) }
597 598 599
            let!(:test_job) do
              create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'deploy',
                                stage: 'deploy', stage_idx: 1,
600
                                options: { script: ['bash'], dependencies: [job2.name] })
601 602 603 604 605 606
            end

            before do
              job.success
              job2.success
            end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
607 608 609 610

            it 'returns dependent jobs' do
              request_job

611
              expect(response).to have_gitlab_http_status(201)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
612
              expect(json_response['id']).to eq(test_job.id)
613
              expect(json_response['dependencies'].count).to eq(1)
614
              expect(json_response['dependencies'][0]).to include('id' => job2.id, 'name' => job2.name, 'token' => job2.token)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
615 616 617
            end
          end

618
          context 'when dependencies is an empty array' do
619 620
            let!(:job) { create(:ci_build, :tag, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
            let!(:job2) { create(:ci_build, :tag, pipeline: pipeline, name: 'rubocop', stage: 'test', stage_idx: 0) }
621 622 623
            let!(:empty_dependencies_job) do
              create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'empty_dependencies_job',
                                stage: 'deploy', stage_idx: 1,
624
                                options: { script: ['bash'], dependencies: [] })
625 626 627 628 629 630 631 632 633 634
            end

            before do
              job.success
              job2.success
            end

            it 'returns an empty array' do
              request_job

635
              expect(response).to have_gitlab_http_status(201)
636 637 638 639 640
              expect(json_response['id']).to eq(empty_dependencies_job.id)
              expect(json_response['dependencies'].count).to eq(0)
            end
          end

Tomasz Maczukin's avatar
Tomasz Maczukin committed
641
          context 'when job has no tags' do
642 643 644
            before do
              job.update(tags: [])
            end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
645 646

            context 'when runner is allowed to pick untagged jobs' do
647 648 649
              before do
                runner.update_column(:run_untagged, true)
              end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
650 651 652

              it 'picks job' do
                request_job
653

654
                expect(response).to have_gitlab_http_status 201
Tomasz Maczukin's avatar
Tomasz Maczukin committed
655 656 657 658
              end
            end

            context 'when runner is not allowed to pick untagged jobs' do
659 660 661
              before do
                runner.update_column(:run_untagged, false)
              end
662

Tomasz Maczukin's avatar
Tomasz Maczukin committed
663 664 665 666 667
              it_behaves_like 'no jobs available'
            end
          end

          context 'when triggered job is available' do
668
            let(:expected_variables) do
669 670 671
              [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true },
               { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true },
               { 'key' => 'CI_PIPELINE_TRIGGERED', 'value' => 'true', 'public' => true },
672 673 674 675 676
               { 'key' => 'DB_NAME', 'value' => 'postgres', 'public' => true },
               { 'key' => 'SECRET_KEY', 'value' => 'secret_value', 'public' => false },
               { 'key' => 'TRIGGER_KEY_1', 'value' => 'TRIGGER_VALUE_1', 'public' => false }]
            end

677 678 679
            let(:trigger) { create(:ci_trigger, project: project) }
            let!(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, builds: [job], trigger: trigger) }

Tomasz Maczukin's avatar
Tomasz Maczukin committed
680 681 682 683
            before do
              project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
            end

684 685 686
            shared_examples 'expected variables behavior' do
              it 'returns variables for triggers' do
                request_job
Tomasz Maczukin's avatar
Tomasz Maczukin committed
687

688
                expect(response).to have_gitlab_http_status(201)
689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
                expect(json_response['variables']).to include(*expected_variables)
              end
            end

            context 'when variables are stored in trigger_request' do
              before do
                trigger_request.update_attribute(:variables, { TRIGGER_KEY_1: 'TRIGGER_VALUE_1' } )
              end

              it_behaves_like 'expected variables behavior'
            end

            context 'when variables are stored in pipeline_variables' do
              before do
                create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
              end

              it_behaves_like 'expected variables behavior'
Tomasz Maczukin's avatar
Tomasz Maczukin committed
707 708 709 710 711 712
            end
          end

          describe 'registry credentials support' do
            let(:registry_url) { 'registry.example.com:5005' }
            let(:registry_credentials) do
Tomasz Maczukin's avatar
Tomasz Maczukin committed
713 714 715 716
              { 'type' => 'registry',
                'url' => registry_url,
                'username' => 'gitlab-ci-token',
                'password' => job.token }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
717 718 719
            end

            context 'when registry is enabled' do
720 721 722
              before do
                stub_container_registry_config(enabled: true, host_port: registry_url)
              end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
723 724 725

              it 'sends registry credentials key' do
                request_job
726

Tomasz Maczukin's avatar
Tomasz Maczukin committed
727 728 729 730 731 732
                expect(json_response).to have_key('credentials')
                expect(json_response['credentials']).to include(registry_credentials)
              end
            end

            context 'when registry is disabled' do
733 734 735
              before do
                stub_container_registry_config(enabled: false, host_port: registry_url)
              end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
736 737 738

              it 'does not send registry credentials' do
                request_job
739

Tomasz Maczukin's avatar
Tomasz Maczukin committed
740 741 742 743 744
                expect(json_response).to have_key('credentials')
                expect(json_response['credentials']).not_to include(registry_credentials)
              end
            end
          end
745 746 747 748 749 750 751 752 753 754 755 756 757

          describe 'timeout support' do
            context 'when project specifies job timeout' do
              let(:project) { create(:project, shared_runners_enabled: false, build_timeout: 1234) }

              it 'contains info about timeout taken from project' do
                request_job

                expect(response).to have_gitlab_http_status(201)
                expect(json_response['runner_info']).to include({ 'timeout' => 1234 })
              end

              context 'when runner specifies lower timeout' do
758
                let(:runner) { create(:ci_runner, :project, maximum_timeout: 1000, projects: [project]) }
759 760 761 762 763 764 765 766 767 768

                it 'contains info about timeout overridden by runner' do
                  request_job

                  expect(response).to have_gitlab_http_status(201)
                  expect(json_response['runner_info']).to include({ 'timeout' => 1000 })
                end
              end

              context 'when runner specifies bigger timeout' do
769
                let(:runner) { create(:ci_runner, :project, maximum_timeout: 2000, projects: [project]) }
770 771 772 773 774 775 776 777 778 779

                it 'contains info about timeout not overridden by runner' do
                  request_job

                  expect(response).to have_gitlab_http_status(201)
                  expect(json_response['runner_info']).to include({ 'timeout' => 1234 })
                end
              end
            end
          end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
780 781 782 783
        end

        def request_job(token = runner.token, **params)
          new_params = params.merge(token: token, last_update: last_update)
blackst0ne's avatar
blackst0ne committed
784
          post api('/jobs/request'), params: new_params, headers: { 'User-Agent' => user_agent }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
785 786 787
        end
      end
    end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
788 789

    describe 'PUT /api/v4/jobs/:id' do
790
      let(:job) { create(:ci_build, :pending, :trace_live, pipeline: pipeline, runner_id: runner.id) }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
791

792 793 794
      before do
        job.run!
      end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
795 796 797 798

      context 'when status is given' do
        it 'mark job as succeeded' do
          update_job(state: 'success')
799

800 801
          job.reload
          expect(job).to be_success
Tomasz Maczukin's avatar
Tomasz Maczukin committed
802 803 804 805
        end

        it 'mark job as failed' do
          update_job(state: 'failed')
806

807 808 809
          job.reload
          expect(job).to be_failed
          expect(job).to be_unknown_failure
Tomasz Maczukin's avatar
Tomasz Maczukin committed
810
        end
811

812 813 814 815 816 817 818 819
        context 'when failure_reason is script_failure' do
          before do
            update_job(state: 'failed', failure_reason: 'script_failure')
            job.reload
          end

          it { expect(job).to be_script_failure }
        end
820

821 822 823 824
        context 'when failure_reason is runner_system_failure' do
          before do
            update_job(state: 'failed', failure_reason: 'runner_system_failure')
            job.reload
825
          end
826 827

          it { expect(job).to be_runner_system_failure }
Tomasz Maczukin's avatar
Tomasz Maczukin committed
828
        end
829 830 831 832 833 834 835 836 837

        context 'when failure_reason is unrecognized value' do
          before do
            update_job(state: 'failed', failure_reason: 'what_is_this')
            job.reload
          end

          it { expect(job).to be_unknown_failure }
        end
838 839 840 841 842 843 844 845 846

        context 'when failure_reason is job_execution_timeout' do
          before do
            update_job(state: 'failed', failure_reason: 'job_execution_timeout')
            job.reload
          end

          it { expect(job).to be_job_execution_timeout }
        end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
847 848
      end

849
      context 'when trace is given' do
850
        it 'creates a trace artifact' do
851
          allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do
852
            ArchiveTraceWorker.new.perform(job.id)
853 854 855
          end

          update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
Tomasz Maczukin's avatar
Tomasz Maczukin committed
856

857
          job.reload
858
          expect(response).to have_gitlab_http_status(200)
859 860
          expect(job.trace.raw).to eq 'BUILD TRACE UPDATED'
          expect(job.job_artifacts_trace.open.read).to eq 'BUILD TRACE UPDATED'
Tomasz Maczukin's avatar
Tomasz Maczukin committed
861
        end
Kamil Trzciński's avatar
Kamil Trzciński committed
862 863 864 865 866 867 868 869 870 871 872 873

        context 'when concurrent update of trace is happening' do
          before do
            job.trace.write('wb') do
              update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
            end
          end

          it 'returns that operation conflicts' do
            expect(response.status).to eq(409)
          end
        end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
874 875 876 877 878
      end

      context 'when no trace is given' do
        it 'does not override trace information' do
          update_job
879

880
          expect(job.reload.trace.raw).to eq 'BUILD TRACE'
Tomasz Maczukin's avatar
Tomasz Maczukin committed
881
        end
882 883 884 885 886 887 888 889 890 891 892 893

        context 'when running state is sent' do
          it 'updates update_at value' do
            expect { update_job_after_time }.to change { job.reload.updated_at }
          end
        end

        context 'when other state is sent' do
          it "doesn't update update_at value" do
            expect { update_job_after_time(20.minutes, state: 'success') }.not_to change { job.reload.updated_at }
          end
        end
Tomasz Maczukin's avatar
Tomasz Maczukin committed
894 895 896 897 898 899 900
      end

      context 'when job has been erased' do
        let(:job) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }

        it 'responds with forbidden' do
          update_job
901

902
          expect(response).to have_gitlab_http_status(403)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
903 904 905
        end
      end

Shinya Maeda's avatar
Shinya Maeda committed
906
      context 'when job has already been finished' do
Shinya Maeda's avatar
Shinya Maeda committed
907 908 909 910 911 912 913 914
        before do
          job.trace.set('Job failed')
          job.drop!(:script_failure)
        end

        it 'does not update job status and job trace' do
          update_job(state: 'success', trace: 'BUILD TRACE UPDATED')

915
          job.reload
Shinya Maeda's avatar
Shinya Maeda committed
916
          expect(response).to have_gitlab_http_status(403)
917
          expect(response.header['Job-Status']).to eq 'failed'
Shinya Maeda's avatar
Shinya Maeda committed
918 919 920 921 922
          expect(job.trace.raw).to eq 'Job failed'
          expect(job).to be_failed
        end
      end

Tomasz Maczukin's avatar
Tomasz Maczukin committed
923 924
      def update_job(token = job.token, **params)
        new_params = params.merge(token: token)
blackst0ne's avatar
blackst0ne committed
925
        put api("/jobs/#{job.id}"), params: new_params
Tomasz Maczukin's avatar
Tomasz Maczukin committed
926
      end
927

928
      def update_job_after_time(update_interval = 20.minutes, state = 'running')
929
        Timecop.travel(job.updated_at + update_interval) do
930
          update_job(job.token, state: state)
931 932 933 934
        end
      end
    end

Tomasz Maczukin's avatar
Tomasz Maczukin committed
935
    describe 'PATCH /api/v4/jobs/:id/trace' do
Kamil Trzciński's avatar