BigW Consortium Gitlab
Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
gitlab-ce
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Forest Godfrey
gitlab-ce
Commits
8631779b
Commit
8631779b
authored
Apr 07, 2017
by
Kamil Trzciński
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'bvl-fix-project-ci-status-cache' into 'master'
Fix invalidating Project build status cache to often See merge request !10313
parents
46aadc5c
9eded57d
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
148 additions
and
116 deletions
+148
-116
pipeline.rb
app/models/ci/pipeline.rb
+0
-6
project.rb
app/models/project.rb
+1
-1
expire_pipeline_cache_service.rb
app/services/ci/expire_pipeline_cache_service.rb
+2
-0
project.rb
features/steps/shared/project.rb
+2
-1
project_pipeline_status.rb
lib/gitlab/cache/ci/project_pipeline_status.rb
+96
-79
projects_spec.rb
spec/features/dashboard/projects_spec.rb
+7
-4
ci_status_helper_spec.rb
spec/helpers/ci_status_helper_spec.rb
+5
-1
projects_helper_spec.rb
spec/helpers/projects_helper_spec.rb
+1
-1
project_pipeline_status_spec.rb
spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+25
-8
pipeline_spec.rb
spec/models/ci/pipeline_spec.rb
+1
-14
project_spec.rb
spec/models/project_spec.rb
+1
-1
expire_pipeline_cache_service_spec.rb
spec/services/ci/expire_pipeline_cache_service_spec.rb
+7
-0
No files found.
app/models/ci/pipeline.rb
View file @
8631779b
...
...
@@ -31,7 +31,6 @@ module Ci
validate
:valid_commit_sha
,
unless: :importing?
after_create
:keep_around_commits
,
unless: :importing?
after_create
:refresh_build_status_cache
state_machine
:status
,
initial: :created
do
event
:enqueue
do
...
...
@@ -351,7 +350,6 @@ module Ci
when
'manual'
then
block
end
end
refresh_build_status_cache
end
def
predefined_variables
...
...
@@ -393,10 +391,6 @@ module Ci
.
fabricate!
end
def
refresh_build_status_cache
Ci
::
PipelineStatus
.
new
(
project
,
sha:
sha
,
status:
status
).
store_in_cache_if_needed
end
private
def
pipeline_data
...
...
app/models/project.rb
View file @
8631779b
...
...
@@ -1182,7 +1182,7 @@ class Project < ActiveRecord::Base
end
def
pipeline_status
@pipeline_status
||=
Ci
::
PipelineStatus
.
load_for_project
(
self
)
@pipeline_status
||=
Gitlab
::
Cache
::
Ci
::
Project
PipelineStatus
.
load_for_project
(
self
)
end
def
mark_import_as_failed
(
error_message
)
...
...
app/services/ci/expire_pipeline_cache_service.rb
View file @
8631779b
...
...
@@ -10,6 +10,8 @@ module Ci
store
.
touch
(
commit_pipelines_path
)
if
pipeline
.
commit
store
.
touch
(
new_merge_request_pipelines_path
)
merge_requests_pipelines_paths
.
each
{
|
path
|
store
.
touch
(
path
)
}
Gitlab
::
Cache
::
Ci
::
ProjectPipelineStatus
.
update_for_pipeline
(
@pipeline
)
end
private
...
...
features/steps/shared/project.rb
View file @
8631779b
...
...
@@ -251,7 +251,8 @@ module SharedProject
step
'project "Shop" has CI build'
do
project
=
Project
.
find_by
(
name:
"Shop"
)
create
:ci_pipeline
,
project:
project
,
sha:
project
.
commit
.
sha
,
ref:
'master'
,
status:
'skipped'
pipeline
=
create
:ci_pipeline
,
project:
project
,
sha:
project
.
commit
.
sha
,
ref:
'master'
pipeline
.
skip
end
step
'I should see last commit with CI status'
do
...
...
app/models/ci/
pipeline_status.rb
→
lib/gitlab/cache/ci/project_
pipeline_status.rb
View file @
8631779b
# This class is not backed by a table in the main database.
# It loads the latest Pipeline for the HEAD of a repository, and caches that
# in Redis.
module
Ci
class
PipelineStatus
attr_accessor
:sha
,
:status
,
:project
,
:loaded
delegate
:commit
,
to: :project
def
self
.
load_for_project
(
project
)
new
(
project
).
tap
do
|
status
|
status
.
load_status
end
end
def
initialize
(
project
,
sha:
nil
,
status:
nil
)
@project
=
project
@sha
=
sha
@status
=
status
end
def
has_status?
loaded?
&&
sha
.
present?
&&
status
.
present?
end
def
load_status
return
if
loaded?
if
has_cache?
load_from_cache
else
load_from_commit
store_in_cache
end
self
.
loaded
=
true
end
def
load_from_commit
return
unless
commit
self
.
sha
=
commit
.
sha
self
.
status
=
commit
.
status
end
# We only cache the status for the HEAD commit of a project
# This status is rendered in project lists
def
store_in_cache_if_needed
return
unless
sha
return
delete_from_cache
unless
commit
store_in_cache
if
commit
.
sha
==
self
.
sha
end
def
load_from_cache
Gitlab
::
Redis
.
with
do
|
redis
|
self
.
sha
,
self
.
status
=
redis
.
hmget
(
cache_key
,
:sha
,
:status
)
end
end
def
store_in_cache
Gitlab
::
Redis
.
with
do
|
redis
|
redis
.
mapped_hmset
(
cache_key
,
{
sha:
sha
,
status:
status
})
end
end
def
delete_from_cache
Gitlab
::
Redis
.
with
do
|
redis
|
redis
.
del
(
cache_key
)
end
end
def
has_cache?
Gitlab
::
Redis
.
with
do
|
redis
|
redis
.
exists
(
cache_key
)
module
Gitlab
module
Cache
module
Ci
class
ProjectPipelineStatus
attr_accessor
:sha
,
:status
,
:ref
,
:project
,
:loaded
delegate
:commit
,
to: :project
def
self
.
load_for_project
(
project
)
new
(
project
).
tap
do
|
status
|
status
.
load_status
end
end
def
self
.
update_for_pipeline
(
pipeline
)
new
(
pipeline
.
project
,
sha:
pipeline
.
sha
,
status:
pipeline
.
status
,
ref:
pipeline
.
ref
).
store_in_cache_if_needed
end
def
initialize
(
project
,
sha:
nil
,
status:
nil
,
ref:
nil
)
@project
=
project
@sha
=
sha
@ref
=
ref
@status
=
status
end
def
has_status?
loaded?
&&
sha
.
present?
&&
status
.
present?
end
def
load_status
return
if
loaded?
if
has_cache?
load_from_cache
else
load_from_project
store_in_cache
end
self
.
loaded
=
true
end
def
load_from_project
return
unless
commit
self
.
sha
=
commit
.
sha
self
.
status
=
commit
.
status
self
.
ref
=
project
.
default_branch
end
# We only cache the status for the HEAD commit of a project
# This status is rendered in project lists
def
store_in_cache_if_needed
return
delete_from_cache
unless
commit
return
unless
sha
return
unless
ref
if
commit
.
sha
==
sha
&&
project
.
default_branch
==
ref
store_in_cache
end
end
def
load_from_cache
Gitlab
::
Redis
.
with
do
|
redis
|
self
.
sha
,
self
.
status
,
self
.
ref
=
redis
.
hmget
(
cache_key
,
:sha
,
:status
,
:ref
)
end
end
def
store_in_cache
Gitlab
::
Redis
.
with
do
|
redis
|
redis
.
mapped_hmset
(
cache_key
,
{
sha:
sha
,
status:
status
,
ref:
ref
})
end
end
def
delete_from_cache
Gitlab
::
Redis
.
with
do
|
redis
|
redis
.
del
(
cache_key
)
end
end
def
has_cache?
Gitlab
::
Redis
.
with
do
|
redis
|
redis
.
exists
(
cache_key
)
end
end
def
loaded?
self
.
loaded
end
def
cache_key
"projects/
#{
project
.
id
}
/build_status"
end
end
end
def
loaded?
self
.
loaded
end
def
cache_key
"projects/
#{
project
.
id
}
/build_status"
end
end
end
spec/features/dashboard/projects_spec.rb
View file @
8631779b
...
...
@@ -7,7 +7,6 @@ RSpec.describe 'Dashboard Projects', feature: true do
before
do
project
.
team
<<
[
user
,
:developer
]
login_as
user
visit
dashboard_projects_path
end
it
'shows the project the user in a member of in the list'
do
...
...
@@ -15,15 +14,19 @@ RSpec.describe 'Dashboard Projects', feature: true do
expect
(
page
).
to
have_content
(
'awesome stuff'
)
end
describe
"with a pipeline"
do
let
(
:pipeline
)
{
create
(
:ci_pipeline
,
:success
,
project:
project
,
sha:
project
.
commit
.
sha
)
}
describe
"with a pipeline"
,
redis:
true
do
let
!
(
:pipeline
)
{
create
(
:ci_pipeline
,
project:
project
,
sha:
project
.
commit
.
sha
)
}
before
do
pipeline
# Since the cache isn't updated when a new pipeline is created
# we need the pipeline to advance in the pipeline since the cache was created
# by visiting the login page.
pipeline
.
succeed
end
it
'shows that the last pipeline passed'
do
visit
dashboard_projects_path
expect
(
page
).
to
have_xpath
(
"//a[@href='
#{
pipelines_namespace_project_commit_path
(
project
.
namespace
,
project
,
project
.
commit
)
}
']"
)
end
end
...
...
spec/helpers/ci_status_helper_spec.rb
View file @
8631779b
...
...
@@ -19,7 +19,11 @@ describe CiStatusHelper do
describe
"#pipeline_status_cache_key"
do
it
"builds a cache key for pipeline status"
do
pipeline_status
=
Ci
::
PipelineStatus
.
new
(
build
(
:project
),
sha:
"123abc"
,
status:
"success"
)
pipeline_status
=
Gitlab
::
Cache
::
Ci
::
ProjectPipelineStatus
.
new
(
build
(
:project
),
sha:
"123abc"
,
status:
"success"
)
expect
(
helper
.
pipeline_status_cache_key
(
pipeline_status
)).
to
eq
(
"pipeline-status/123abc-success"
)
end
end
...
...
spec/helpers/projects_helper_spec.rb
View file @
8631779b
...
...
@@ -63,7 +63,7 @@ describe ProjectsHelper do
end
end
describe
"#project_list_cache_key"
do
describe
"#project_list_cache_key"
,
redis:
true
do
let
(
:project
)
{
create
(
:project
)
}
it
"includes the namespace"
do
...
...
spec/
models/ci/
pipeline_status_spec.rb
→
spec/
lib/gitlab/cache/ci/project_
pipeline_status_spec.rb
View file @
8631779b
require
'spec_helper'
describe
Ci
::
PipelineStatus
do
describe
Gitlab
::
Cache
::
Ci
::
Project
PipelineStatus
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:pipeline_status
)
{
described_class
.
new
(
project
)
}
...
...
@@ -12,6 +12,20 @@ describe Ci::PipelineStatus do
end
end
describe
'.update_for_pipeline'
do
it
'refreshes the cache if nescessary'
do
pipeline
=
build_stubbed
(
:ci_pipeline
,
sha:
'123456'
,
status:
'success'
)
fake_status
=
double
expect
(
described_class
).
to
receive
(
:new
).
with
(
pipeline
.
project
,
sha:
'123456'
,
status:
'success'
,
ref:
'master'
).
and_return
(
fake_status
)
expect
(
fake_status
).
to
receive
(
:store_in_cache_if_needed
)
described_class
.
update_for_pipeline
(
pipeline
)
end
end
describe
'#has_status?'
do
it
"is false when the status wasn't loaded yet"
do
expect
(
pipeline_status
.
has_status?
).
to
be_falsy
...
...
@@ -41,14 +55,14 @@ describe Ci::PipelineStatus do
it
'loads the status from the project commit when there is no cache'
do
allow
(
pipeline_status
).
to
receive
(
:has_cache?
).
and_return
(
false
)
expect
(
pipeline_status
).
to
receive
(
:load_from_
commi
t
)
expect
(
pipeline_status
).
to
receive
(
:load_from_
projec
t
)
pipeline_status
.
load_status
end
it
'stores the status in the cache when it loading it from the project'
do
allow
(
pipeline_status
).
to
receive
(
:has_cache?
).
and_return
(
false
)
allow
(
pipeline_status
).
to
receive
(
:load_from_
commi
t
)
allow
(
pipeline_status
).
to
receive
(
:load_from_
projec
t
)
expect
(
pipeline_status
).
to
receive
(
:store_in_cache
)
...
...
@@ -70,14 +84,15 @@ describe Ci::PipelineStatus do
end
end
describe
"#load_from_
commi
t"
do
describe
"#load_from_
projec
t"
do
let!
(
:pipeline
)
{
create
(
:ci_pipeline
,
:success
,
project:
project
,
sha:
project
.
commit
.
sha
)
}
it
'reads the status from the pipeline for the commit'
do
pipeline_status
.
load_from_
commi
t
pipeline_status
.
load_from_
projec
t
expect
(
pipeline_status
.
status
).
to
eq
(
'success'
)
expect
(
pipeline_status
.
sha
).
to
eq
(
project
.
commit
.
sha
)
expect
(
pipeline_status
.
ref
).
to
eq
(
project
.
default_branch
)
end
it
"doesn't fail for an empty project"
do
...
...
@@ -108,10 +123,11 @@ describe Ci::PipelineStatus do
build_status
=
described_class
.
load_for_project
(
project
)
build_status
.
store_in_cache_if_needed
sha
,
status
=
Gitlab
::
Redis
.
with
{
|
redis
|
redis
.
hmget
(
"projects/
#{
project
.
id
}
/build_status"
,
:sha
,
:status
)
}
sha
,
status
,
ref
=
Gitlab
::
Redis
.
with
{
|
redis
|
redis
.
hmget
(
"projects/
#{
project
.
id
}
/build_status"
,
:sha
,
:status
,
:ref
)
}
expect
(
sha
).
not_to
be_nil
expect
(
status
).
not_to
be_nil
expect
(
ref
).
not_to
be_nil
end
it
"doesn't store the status in redis when the sha is not the head of the project"
do
...
...
@@ -126,14 +142,15 @@ describe Ci::PipelineStatus do
it
"deletes the cache if the repository doesn't have a head commit"
do
empty_project
=
create
(
:empty_project
)
Gitlab
::
Redis
.
with
{
|
redis
|
redis
.
mapped_hmset
(
"projects/
#{
empty_project
.
id
}
/build_status"
,
{
sha:
"sha"
,
status:
"pending"
})
}
Gitlab
::
Redis
.
with
{
|
redis
|
redis
.
mapped_hmset
(
"projects/
#{
empty_project
.
id
}
/build_status"
,
{
sha:
"sha"
,
status:
"pending"
,
ref:
'master'
})
}
other_status
=
described_class
.
new
(
empty_project
,
sha:
"123456"
,
status:
"failed"
)
other_status
.
store_in_cache_if_needed
sha
,
status
=
Gitlab
::
Redis
.
with
{
|
redis
|
redis
.
hmget
(
"projects/
#{
empty_project
.
id
}
/build_status"
,
:sha
,
:status
)
}
sha
,
status
,
ref
=
Gitlab
::
Redis
.
with
{
|
redis
|
redis
.
hmget
(
"projects/
#{
empty_project
.
id
}
/build_status"
,
:sha
,
:status
,
:ref
)
}
expect
(
sha
).
to
be_nil
expect
(
status
).
to
be_nil
expect
(
ref
).
to
be_nil
end
end
...
...
spec/models/ci/pipeline_spec.rb
View file @
8631779b
...
...
@@ -375,7 +375,7 @@ describe Ci::Pipeline, models: true do
end
end
describe
'pipeline
ETag
caching'
do
describe
'pipeline caching'
do
it
'executes ExpirePipelinesCacheService'
do
expect_any_instance_of
(
Ci
::
ExpirePipelineCacheService
).
to
receive
(
:execute
).
with
(
pipeline
)
...
...
@@ -1079,19 +1079,6 @@ describe Ci::Pipeline, models: true do
end
end
describe
'#update_status'
do
let
(
:pipeline
)
{
create
(
:ci_pipeline
,
sha:
'123456'
)
}
it
'updates the cached status'
do
fake_status
=
double
# after updating the status, the status is set to `skipped` for this pipeline's builds
expect
(
Ci
::
PipelineStatus
).
to
receive
(
:new
).
with
(
pipeline
.
project
,
sha:
'123456'
,
status:
'skipped'
).
and_return
(
fake_status
)
expect
(
fake_status
).
to
receive
(
:store_in_cache_if_needed
)
pipeline
.
update_status
end
end
describe
'notifications when pipeline success or failed'
do
let
(
:project
)
{
create
(
:project
,
:repository
)
}
...
...
spec/models/project_spec.rb
View file @
8631779b
...
...
@@ -1874,7 +1874,7 @@ describe Project, models: true do
describe
'#pipeline_status'
do
let
(
:project
)
{
create
(
:project
)
}
it
'builds a pipeline status'
do
expect
(
project
.
pipeline_status
).
to
be_a
(
Ci
::
PipelineStatus
)
expect
(
project
.
pipeline_status
).
to
be_a
(
Gitlab
::
Cache
::
Ci
::
Project
PipelineStatus
)
end
it
'hase a loaded pipeline status'
do
...
...
spec/services/ci/expire_pipeline_cache_service_spec.rb
View file @
8631779b
...
...
@@ -16,5 +16,12 @@ describe Ci::ExpirePipelineCacheService, services: true do
subject
.
execute
(
pipeline
)
end
it
'updates the cached status for a project'
do
expect
(
Gitlab
::
Cache
::
Ci
::
ProjectPipelineStatus
).
to
receive
(
:update_for_pipeline
).
with
(
pipeline
)
subject
.
execute
(
pipeline
)
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment