Commit 94697284 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee into ce-to-ee-2017-12-12[ci skip]

parents f42fecc0 025bdb21
...@@ -8,11 +8,8 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -8,11 +8,8 @@ class Projects::ClustersController < Projects::ApplicationController
STATUS_POLLING_INTERVAL = 10_000 STATUS_POLLING_INTERVAL = 10_000
def index def index
@scope = params[:scope] || 'all' clusters = ClustersFinder.new(project, current_user, :all).execute
@clusters = ClustersFinder.new(project, current_user, @scope).execute.page(params[:page]) @clusters = clusters.page(params[:page]).per(20)
@active_count = ClustersFinder.new(project, current_user, :active).execute.count
@inactive_count = ClustersFinder.new(project, current_user, :inactive).execute.count
@all_count = @active_count + @inactive_count
end end
def new def new
......
...@@ -2,6 +2,8 @@ module Geo ...@@ -2,6 +2,8 @@ module Geo
module Fdw module Fdw
class LfsObject < ::Geo::BaseFdw class LfsObject < ::Geo::BaseFdw
self.table_name = Gitlab::Geo.fdw_table('lfs_objects') self.table_name = Gitlab::Geo.fdw_table('lfs_objects')
scope :with_files_stored_locally, ->() { where(file_store: [nil, LfsObjectUploader::LOCAL_STORE]) }
end end
end end
end end
...@@ -87,6 +87,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -87,6 +87,14 @@ class MergeRequest < ActiveRecord::Base
transition locked: :opened transition locked: :opened
end end
before_transition any => :opened do |merge_request|
merge_request.merge_jid = nil
merge_request.run_after_commit do
UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
end
end
state :opened state :opened
state :closed state :closed
state :merged state :merged
......
...@@ -87,7 +87,7 @@ module Ci ...@@ -87,7 +87,7 @@ module Ci
end end
def related_merge_requests def related_merge_requests
MergeRequest.where(source_project: pipeline.project, source_branch: pipeline.ref) MergeRequest.opened.where(source_project: pipeline.project, source_branch: pipeline.ref)
end end
end end
end end
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon("angle-left")
.fade-right= icon("angle-right")
%ul.nav-links.scrolling-tabs
%li{ class: ('active' if @scope == 'active') }>
= link_to project_clusters_path(@project, scope: :active), class: "js-active-tab" do
= s_("ClusterIntegration|Active")
%span.badge= @active_count
%li{ class: ('active' if @scope == 'inactive') }>
= link_to project_clusters_path(@project, scope: :inactive), class: "js-inactive-tab" do
= s_("ClusterIntegration|Inactive")
%span.badge= @inactive_count
%li{ class: ('active' if @scope.nil? || @scope == 'all') }>
= link_to project_clusters_path(@project), class: "js-all-tab" do
= s_("ClusterIntegration|All")
%span.badge= @all_count
.nav-controls
- if @project.feature_available?(:multiple_clusters)
= link_to s_("ClusterIntegration|Add cluster"), new_project_cluster_path(@project), class: "btn btn-success btn-add-cluster has-tooltip js-add-cluster"
- else
= link_to s_("ClusterIntegration|Add cluster"), new_project_cluster_path(@project), class: "btn btn-success btn-add-cluster disabled has-tooltip js-add-cluster", title: s_("ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate")
...@@ -2,9 +2,13 @@ ...@@ -2,9 +2,13 @@
- page_title "Clusters" - page_title "Clusters"
.clusters-container .clusters-container
- if !@clusters.empty? - if @clusters.empty?
= render "tabs" = render "empty_state"
.ci-table.js-clusters-list - else
.top-area.adjust
.nav-text
= s_("ClusterIntegration|Clusters can be used to deploy applications and to provide Review Apps for this project")
= render 'projects/ee/clusters/buttons', project: @project
.gl-responsive-table-row.table-row-header{ role: "row" } .gl-responsive-table-row.table-row-header{ role: "row" }
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
= s_("ClusterIntegration|Cluster") = s_("ClusterIntegration|Cluster")
...@@ -16,9 +20,3 @@ ...@@ -16,9 +20,3 @@
- @clusters.each do |cluster| - @clusters.each do |cluster|
= render "cluster", cluster: cluster.present(current_user: current_user) = render "cluster", cluster: cluster.present(current_user: current_user)
= paginate @clusters, theme: "gitlab" = paginate @clusters, theme: "gitlab"
- elsif @scope == 'all'
= render "empty_state"
- else
= render "tabs"
.prepend-top-20.text-center
= s_("ClusterIntegration|There are no clusters to show")
...@@ -12,8 +12,16 @@ module Geo ...@@ -12,8 +12,16 @@ module Geo
{ id: object_db_id, type: object_type, job_id: job_id } if job_id { id: object_db_id, type: object_type, job_id: job_id } if job_id
end end
def finder def attachments_finder
@finder ||= FileRegistryFinder.new(current_node: current_node) @attachments_finder ||= AttachmentRegistryFinder.new(current_node: current_node)
end
def file_registry_finder
@file_registry_finder ||= FileRegistryFinder.new(current_node: current_node)
end
def lfs_objects_finder
@lfs_objects_finder ||= LfsObjectRegistryFinder.new(current_node: current_node)
end end
# Pools for new resources to be transferred # Pools for new resources to be transferred
...@@ -26,19 +34,36 @@ module Geo ...@@ -26,19 +34,36 @@ module Geo
if remaining_capacity.zero? if remaining_capacity.zero?
resources resources
else else
resources + finder.find_failed_objects(batch_size: remaining_capacity) resources + find_failed_upload_object_ids(batch_size: remaining_capacity)
end end
end end
def find_unsynced_objects(batch_size:) def find_unsynced_objects(batch_size:)
lfs_object_ids = finder.find_nonreplicated_lfs_objects(batch_size: batch_size, except_registry_ids: scheduled_file_ids(:lfs)) lfs_object_ids = find_unsynced_lfs_objects_ids(batch_size: batch_size)
upload_objects_ids = finder.find_nonreplicated_uploads(batch_size: batch_size, except_registry_ids: scheduled_file_ids(Geo::FileService::DEFAULT_OBJECT_TYPES)) attachment_ids = find_unsynced_attachments_ids(batch_size: batch_size)
interleave(lfs_object_ids, attachment_ids)
end
def find_unsynced_lfs_objects_ids(batch_size:)
lfs_objects_finder.find_unsynced_lfs_objects(batch_size: batch_size, except_registry_ids: scheduled_file_ids(:lfs))
.pluck(:id)
.map { |id| [id, :lfs] }
end
def find_unsynced_attachments_ids(batch_size:)
attachments_finder.find_unsynced_attachments(batch_size: batch_size, except_registry_ids: scheduled_file_ids(Geo::FileService::DEFAULT_OBJECT_TYPES))
.pluck(:id, :uploader)
.map { |id, uploader| [id, uploader.sub(/Uploader\z/, '').underscore] }
end
interleave(lfs_object_ids, upload_objects_ids) def find_failed_upload_object_ids(batch_size:)
file_registry_finder.find_failed_file_registries(batch_size: batch_size)
.pluck(:file_id, :file_type)
end end
def scheduled_file_ids(file_types) def scheduled_file_ids(file_types)
file_types = Array(file_types) unless file_types.is_a? Array file_types = Array(file_types)
scheduled_jobs.select { |data| file_types.include?(data[:type]) }.map { |data| data[:id] } scheduled_jobs.select { |data| file_types.include?(data[:type]) }.map { |data| data[:id] }
end end
......
...@@ -23,7 +23,12 @@ class StuckMergeJobsWorker ...@@ -23,7 +23,12 @@ class StuckMergeJobsWorker
merge_requests = MergeRequest.where(id: completed_ids) merge_requests = MergeRequest.where(id: completed_ids)
merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged) merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged)
merge_requests.where(merge_commit_sha: nil).update_all(state: :opened, merge_jid: nil)
merge_requests_to_reopen = merge_requests.where(merge_commit_sha: nil)
# Do not reopen merge requests using direct queries.
# We rely on state machine callbacks to update head_pipeline_id
merge_requests_to_reopen.each(&:unlock_mr)
Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}") Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}")
end end
......
...@@ -8,8 +8,19 @@ class UpdateHeadPipelineForMergeRequestWorker ...@@ -8,8 +8,19 @@ class UpdateHeadPipelineForMergeRequestWorker
pipeline = Ci::Pipeline.where(project: merge_request.source_project, ref: merge_request.source_branch).last pipeline = Ci::Pipeline.where(project: merge_request.source_project, ref: merge_request.source_branch).last
return unless pipeline && pipeline.latest? return unless pipeline && pipeline.latest?
raise ArgumentError, 'merge request sha does not equal pipeline sha' if merge_request.diff_head_sha != pipeline.sha
if merge_request.diff_head_sha != pipeline.sha
log_error_message_for(merge_request)
return
end
merge_request.update_attribute(:head_pipeline_id, pipeline.id) merge_request.update_attribute(:head_pipeline_id, pipeline.id)
end end
def log_error_message_for(merge_request)
Rails.logger.error(
"Outdated head pipeline for active merge request: id=#{merge_request.id}, source_branch=#{merge_request.source_branch}, diff_head_sha=#{merge_request.diff_head_sha}"
)
end
end end
---
title: Geo - Fix difference in FDW / non-FDW queries for Geo::FileRegistry queries
merge_request: 3714
author:
type: fixed
---
title: Present multiple clusters in a single list instead of a tabbed view
merge_request: 15669
author:
type: changed
...@@ -15,3 +15,4 @@ providers. ...@@ -15,3 +15,4 @@ providers.
- [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS - [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS
- [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider - [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [Okta](okta.md) Configure GitLab to sign in using Okta - [Okta](okta.md) Configure GitLab to sign in using Okta
- [Authentiq](authentiq.md): Enable the Authentiq OmniAuth provider for passwordless authentication
...@@ -37,6 +37,7 @@ Follow the steps below to configure an active/active setup: ...@@ -37,6 +37,7 @@ Follow the steps below to configure an active/active setup:
1. [Configure the database](database.md) 1. [Configure the database](database.md)
1. [Configure Redis](redis.md) 1. [Configure Redis](redis.md)
1. [Configure Redis for GitLab source installations](redis_source.md)
1. [Configure NFS](nfs.md) 1. [Configure NFS](nfs.md)
1. [Configure the GitLab application servers](gitlab.md) 1. [Configure the GitLab application servers](gitlab.md)
1. [Configure the load balancers](load_balancer.md) 1. [Configure the load balancers](load_balancer.md)
......
...@@ -19,6 +19,7 @@ Learn how to install, configure, update, and maintain your GitLab instance. ...@@ -19,6 +19,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- **(EES/EEP)** [Omnibus support for external MySQL DB](https://docs.gitlab.com/omnibus/settings/database.html#using-a-mysql-database-management-server-enterprise-edition-only): Omnibus package supports configuring an external MySQL database. - **(EES/EEP)** [Omnibus support for external MySQL DB](https://docs.gitlab.com/omnibus/settings/database.html#using-a-mysql-database-management-server-enterprise-edition-only): Omnibus package supports configuring an external MySQL database.
- **(EES/EEP)** [Omnibus support for log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only) - **(EES/EEP)** [Omnibus support for log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only)
- [High Availability](high_availability/README.md): Configure multiple servers for scaling or high availability. - [High Availability](high_availability/README.md): Configure multiple servers for scaling or high availability.
- [High Availability on AWS](../university/high-availability/aws/README.md): Set up GitLab HA on Amazon AWS.
- **(EEP)** [GitLab GEO](../gitlab-geo/README.md): Replicate your GitLab instance to other geographical locations as a read-only fully operational version. - **(EEP)** [GitLab GEO](../gitlab-geo/README.md): Replicate your GitLab instance to other geographical locations as a read-only fully operational version.
- **(EEP)** [Pivotal Tile](../install/pivotal/index.md): Deploy GitLab as a pre-configured appliance using Ops Manager (BOSH) for Pivotal Cloud Foundry. - **(EEP)** [Pivotal Tile](../install/pivotal/index.md): Deploy GitLab as a pre-configured appliance using Ops Manager (BOSH) for Pivotal Cloud Foundry.
- [High Availability](high_availability/README.md): Configure multiple servers for scaling or high availability. - [High Availability](high_availability/README.md): Configure multiple servers for scaling or high availability.
...@@ -26,8 +27,6 @@ Learn how to install, configure, update, and maintain your GitLab instance. ...@@ -26,8 +27,6 @@ Learn how to install, configure, update, and maintain your GitLab instance.
### Configuring GitLab ### Configuring GitLab
- [Adjust your instance's timezone](../workflow/timezone.md): Customize the default time zone of GitLab. - [Adjust your instance's timezone](../workflow/timezone.md): Customize the default time zone of GitLab.
- [Header logo](../customization/branded_page_and_email_header.md): Change the logo on all pages and email headers.
- [Welcome message](../customization/welcome_message.md): Add a custom welcome message to the sign-in page.
- [System hooks](../system_hooks/system_hooks.md): Notifications when users, projects and keys are changed. - [System hooks](../system_hooks/system_hooks.md): Notifications when users, projects and keys are changed.
- [Security](../security/README.md): Learn what you can do to further secure your GitLab instance. - [Security](../security/README.md): Learn what you can do to further secure your GitLab instance.
- [Usage statistics, version check, and usage ping](../user/admin_area/settings/usage_statistics.md): Enable or disable information about your instance to be sent to GitLab, Inc. - [Usage statistics, version check, and usage ping](../user/admin_area/settings/usage_statistics.md): Enable or disable information about your instance to be sent to GitLab, Inc.
...@@ -38,6 +37,13 @@ Learn how to install, configure, update, and maintain your GitLab instance. ...@@ -38,6 +37,13 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Environment variables](environment_variables.md): Supported environment variables that can be used to override their defaults values in order to configure GitLab. - [Environment variables](environment_variables.md): Supported environment variables that can be used to override their defaults values in order to configure GitLab.
- **(EES/EEP)** [Elasticsearch](../integration/elasticsearch.md): Enable Elasticsearch to empower GitLab's Advanced Global Search. Useful when you deal with a huge amount of data. - **(EES/EEP)** [Elasticsearch](../integration/elasticsearch.md): Enable Elasticsearch to empower GitLab's Advanced Global Search. Useful when you deal with a huge amount of data.
#### Customizing GitLab's appearance
- [Header logo](../customization/branded_page_and_email_header.md): Change the logo on all pages and email headers.
- [Branded login page](../customization/branded_login_page.md): Customize the login page with your own logo, title, and description.
- [Welcome message](../customization/welcome_message.md): Add a custom welcome message to the sign-in page.
- ["New Project" page](../customization/new_project_page.md): Customize the text to be displayed on the page that opens whenever your users create a new project.
### Maintaining GitLab ### Maintaining GitLab
- [Raketasks](../raketasks/README.md): Perform various tasks for maintenance, backups, automatic webhooks setup, etc. - [Raketasks](../raketasks/README.md): Perform various tasks for maintenance, backups, automatic webhooks setup, etc.
...@@ -90,6 +96,7 @@ server with IMAP authentication on Ubuntu, to be used with Reply by email. ...@@ -90,6 +96,7 @@ server with IMAP authentication on Ubuntu, to be used with Reply by email.
- [Issue closing pattern](issue_closing_pattern.md): Customize how to close an issue from commit messages. - [Issue closing pattern](issue_closing_pattern.md): Customize how to close an issue from commit messages.
- [Gitaly](gitaly/index.md): Configuring Gitaly, GitLab's Git repository storage service. - [Gitaly](gitaly/index.md): Configuring Gitaly, GitLab's Git repository storage service.
- [Default labels](../user/admin_area/labels.html): Create labels that will be automatically added to every new project. - [Default labels](../user/admin_area/labels.html): Create labels that will be automatically added to every new project.
- [Restrict the use of public or internal projects](../public_access/public_access.md#restricting-the-use-of-public-or-internal-projects): Restrict the use of visibility levels for users when they create a project or a snippet.
### Repository settings ### Repository settings
......
---
comments: false
---
This document was split into: This document was split into:
- [administration/issue_closing_pattern.md](../administration/issue_closing_pattern.md). - [administration/issue_closing_pattern.md](../administration/issue_closing_pattern.md).
......
...@@ -8,5 +8,5 @@ It is possible to add a markdown-formatted welcome message to your GitLab ...@@ -8,5 +8,5 @@ It is possible to add a markdown-formatted welcome message to your GitLab
sign-in page. Users of GitLab Enterprise Edition should use the [branded login sign-in page. Users of GitLab Enterprise Edition should use the [branded login
page feature](branded_login_page.md) instead. page feature](branded_login_page.md) instead.
The welcome message (extra_sign_in_text) can now be set/changed in the Admin UI. The welcome message (extra_sign_in_text) can now be set/changed in the Admin UI.
Admin area > Settings Admin area > Settings
...@@ -208,21 +208,22 @@ will not be able to perform all necessary configuration steps. Refer to ...@@ -208,21 +208,22 @@ will not be able to perform all necessary configuration steps. Refer to
`netstat -plnt` to make sure that PostgreSQL is listening on port `5432` to `netstat -plnt` to make sure that PostgreSQL is listening on port `5432` to
the primary server's private address. the primary server's private address.
1. Make a copy of the PostgreSQL `server.crt` file 1. Make a copy of the PostgreSQL `server.crt` file.
A certificate was automatically generated when GitLab was reconfigured. This
will be used automatically to protect your PostgreSQL traffic from
eavesdroppers, but to protect against active ("man-in-the-middle") attackers,
the secondary needs a copy of the certificate.
Run this command: A certificate was automatically generated when GitLab was reconfigured. This
will be used automatically to protect your PostgreSQL traffic from
eavesdroppers, but to protect against active ("man-in-the-middle") attackers,
the secondary needs a copy of the certificate.
``` Run this command:
cat ~gitlab-psql/data/server.crt
```
Copy the output into a file on your local computer called `server.crt`. You ```
will need it when setting up the secondary! The certificate is not sensitive cat ~gitlab-psql/data/server.crt
data. ```
Copy the output into a file on your local computer called `server.crt`. You
will need it when setting up the secondary! The certificate is not sensitive
data.
1. Verify that clock synchronization is enabled. 1. Verify that clock synchronization is enabled.
......
# From Community Edition 10.3 to Enterprise Edition 10.3
This guide assumes you have a correctly configured and tested installation of
GitLab Community Edition 10.3. If you run into any trouble or if you have any
questions please contact us at [support@gitlab.com].
### 0. Backup
Make a backup just in case something goes wrong:
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
For installations using MySQL, this may require granting "LOCK TABLES"
privileges to the GitLab user on the database version.
### 1. Stop server
```bash
sudo service gitlab stop
```
### 2. Get the EE code
```bash
cd /home/git/gitlab
sudo -u git -H git remote add -f ee https://gitlab.com/gitlab-org/gitlab-ee.git
sudo -u git -H git checkout 10-3-stable-ee
```
### 3. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without postgres')
sudo -u git -H bundle install --without postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 4. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
### 5. Check application status
Check if GitLab and its environment are configured correctly:
```bash
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
```
To make sure you didn't miss anything run a more thorough check with:
```bash
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
If all items are green, then congratulations upgrade complete!
## Things went south? Revert to previous version (Community Edition 10.3)
### 1. Revert the code to the previous version
```bash
cd /home/git/gitlab
sudo -u git -H git checkout 10-3-stable
```
### 2. Restore from the backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
[support@gitlab.com]: mailto:support@gitlab.com
module Geo module Geo
class AttachmentRegistryFinder < RegistryFinder class AttachmentRegistryFinder < FileRegistryFinder
def attachments
if selective_sync?
Upload.where(group_uploads.or(project_uploads).or(other_uploads))
else
Upload.all
end
end
def count_attachments def count_attachments
uploads.count attachments.count
end end
def count_synced_attachments def count_synced_attachments
...@@ -34,12 +42,25 @@ module Geo ...@@ -34,12 +42,25 @@ module Geo
relation relation
end end
def uploads # Find limited amount of non replicated attachments.
if selective_sync? #
Upload.where(group_uploads.or(project_uploads).or(other_uploads)) # You can pass a list with `except_registry_ids:` so you can exclude items you
else # already scheduled but haven't finished and persisted to the database yet
Upload.all #
end # TODO: Alternative here is to use some sort of window function with a cursor instead
# of simply limiting the query and passing a list of items we don't want
#
# @param [Integer] batch_size used to limit the results returned
# @param [Array<Integer>] except_registry_ids ids that will be ignored from the query
def find_unsynced_attachments(batch_size:, except_registry_ids: [])
relation =
if use_legacy_queries?
legacy_find_unsynced_attachments(except_registry_ids: except_registry_ids)
else
fdw_find_unsynced_attachments(except_registry_ids: except_registry_ids)
end
relation.limit(batch_size)
end end
private private
...@@ -85,29 +106,45 @@ module Geo ...@@ -85,29 +106,45 @@ module Geo
.merge(Geo::FileRegistry.attachments) .merge(Geo::FileRegistry.attachments)
end end
def fdw_find_unsynced_attachments(except_registry_ids:)
fdw_table = Geo::Fdw::Upload.table_name
upload_types = Geo::FileService::DEFAULT_OBJECT_TYPES.map { |val| "'#{val}'" }.join(',')
Geo::Fdw::Upload.joins("LEFT OUTER JOIN file_registry
ON file_registry.file_id = #{fdw_table}.id
AND file_registry.file_type IN (#{upload_types})")
.where(file_registry: { id: nil })
.where.not(id: except_registry_ids)
end
# #
# Legacy accessors (non FDW) # Legacy accessors (non FDW)
# #
def legacy_find_synced_attachments def legacy_find_synced_attachments
legacy_find_attachments(Geo::FileRegistry.attachments.synced.pluck(:file_id)) legacy_inner_join_registry_ids(
attachments,
Geo::FileRegistry.attachments.synced.pluck(:file_id),
Upload
)
end end
def legacy_find_failed_attachments def legacy_find_failed_attachments
legacy_find_attachments(Geo::FileRegistry.attachments.failed.pluck(:file_id)) legacy_inner_join_registry_ids(
attachments,
Geo::FileRegistry.attachments.failed.pluck(:file_id),
Upload
)
end end
def legacy_find_attachments(registry_file_ids) def legacy_find_unsynced_attachments(except_registry_ids:)
return Upload.none if registry_file_ids.empty? registry_ids = legacy_pluck_registry_ids(file_types: Geo::FileService::DEFAULT_OBJECT_TYPES, except_registry_ids: except_registry_ids)
joined_relation = uploads.joins(<<~SQL)
INNER JOIN
(VALUES #{registry_file_ids.map { |id| "(#{id})" }.join(',')})
file_registry(file_id)
ON #{Upload.table_name}.id = file_registry.file_id
SQL
joined_relation legacy_left_outer_join_registry_ids(
attachments,
registry_ids,
Upload
)
end end
end end
end end
module Geo module Geo
class FileRegistryFinder < RegistryFinder class FileRegistryFinder < RegistryFinder
def find_failed_objects(batch_size:) def find_failed_file_registries(batch_size:)
Geo::FileRegistry Geo::FileRegistry.failed.retry_due.limit(batch_size)
.failed
.retry_due
.limit(batch_size)
.pluck(:file_id, :file_type)
end
# Find limited amount of non replicated lfs objects.
#
# You can pass a list with `except_registry_ids:` so you can exclude items you
# already scheduled but haven't finished and persisted to the database yet
#
# TODO: Alternative here is to use some sort of window function with a cursor instead
# of simply limiting the query and passing a list of items we don't want
#
# @param [Integer] batch_size used to limit the results returned
# @param [Array<Integer>] except_registry_ids ids that will be ignored from the query
def find_nonreplicated_lfs_objects(batch_size:, except_registry_ids:)
# Selective project replication adds a wrinkle to FDW queries, so
# we fallback to the legacy version for now.
relation =
if use_legacy_queries?
legacy_find_nonreplicated_lfs_objects(except_registry_ids: except_registry_ids)
else
fdw_find_nonreplicated_lfs_objects
end
relation
.limit(batch_size)
.pluck(:id)
.map { |id| [id, :lfs] }
end
# Find limited amount of non replicated uploads.
#
# You can pass a list with `except_registry_ids:` so you can exclude items you
# already scheduled but haven't finished and persisted to the database yet
#
# TODO: Alternative here is to use some sort of window function with a cursor instead
# of simply limiting the query and passing a list of items we don't want
#
# @param [Integer] batch_size used to limit the results returned
# @param [Array<Integer>] except_registry_ids ids that will be ignored from the query
def find_nonreplicated_uploads(batch_size:, except_registry_ids:)
# Selective project replication adds a wrinkle to FDW queries, so
# we fallback to the legacy version for now.
relation =
if use_legacy_queries?
legacy_find_nonreplicated_uploads(except_registry_ids: except_registry_ids)
else
fdw_find_nonreplicated_uploads
end
relation
.limit(batch_size)
.pluck(:id, :uploader)
.map { |id, uploader| [id, uploader.sub(/Uploader\z/, '').underscore] }
end end
protected protected
#
# FDW accessors
#
def fdw_find_nonreplicated_lfs_objects
fdw_table = Geo::Fdw::LfsObject.table_name
# Filter out objects in object storage (this is done in GeoNode#lfs_objects)
Geo::Fdw::LfsObject.joins("LEFT OUTER JOIN file_registry
ON file_registry.file_id = #{fdw_table}.id
AND file_registry.file_type = 'lfs'")
.where("#{fdw_table}.file_store IS NULL OR #{fdw_table}.file_store = #{LfsObjectUploader::LOCAL_STORE}")
.where('file_registry.file_id IS NULL')
end
def fdw_find_nonreplicated_uploads
fdw_table = Geo::Fdw::Upload.table_name
upload_types = Geo::FileService::DEFAULT_OBJECT_TYPES.map { |val| "'#{val}'" }.join(',')
Geo::Fdw::Upload.joins("LEFT OUTER JOIN file_registry
ON file_registry.file_id = #{fdw_table}.id
AND file_registry.file_type IN (#{upload_types})")
.where('file_registry.file_id IS NULL')
end
#
# Legacy accessors (non FDW)
#
def legacy_find_nonreplicated_lfs_objects(except_registry_ids:)
registry_ids = legacy_pluck_registry_ids(file_types: :lfs, except_registry_ids: except_registry_ids)
legacy_filter_registry_ids(
lfs_objects_finder.lfs_objects,
registry_ids,
LfsObject.table_name
)
end
def legacy_find_nonreplicated_uploads(except_registry_ids:)
registry_ids = legacy_pluck_registry_ids(file_types: Geo::FileService::DEFAULT_OBJECT_TYPES, except_registry_ids: except_registry_ids)
legacy_filter_registry_ids(
attachments_finder.uploads,
registry_ids,
Upload.table_name
)
end
# This query requires data from two different databases, and unavoidably
# plucks a list of file IDs from one into the other. This will not scale
# well with the number of synchronized files--the query will increase
# linearly in size--so this should be replaced with postgres_fdw ASAP.
def legacy_filter_registry_ids(objects, registry_ids, table_name)
return objects if registry_ids.empty?
joined_relation = objects.joins(<<~SQL)
LEFT OUTER JOIN
(VALUES #{registry_ids.map { |id| "(#{id}, 't')" }.join(',')})
file_registry(file_id, registry_present)
ON #{table_name}.id = file_registry.file_id
SQL
joined_relation.where(file_registry: { registry_present: [nil, false] })
end
def legacy_pluck_registry_ids(file_types:, except_registry_ids:) def legacy_pluck_registry_ids(file_types:, except_registry_ids:)
ids = Geo::FileRegistry.where(file_type: file_types).pluck(:file_id) ids = Geo::FileRegistry.where(file_type: file_types).pluck(:file_id)
(ids + except_registry_ids).uniq (ids + except_registry_ids).uniq
end end
def attachments_finder
@attachments_finder ||= AttachmentRegistryFinder.new(current_node: current_node)
end
def lfs_objects_finder
@lfs_objects_finder ||= LfsObjectRegistryFinder.new(current_node: current_node)
end
end end
end end
module Geo module Geo
class LfsObjectRegistryFinder < RegistryFinder class LfsObjectRegistryFinder < FileRegistryFinder
def count_lfs_objects def count_lfs_objects
lfs_objects.count lfs_objects.count
end end
...@@ -26,6 +26,27 @@ module Geo ...@@ -26,6 +26,27 @@ module Geo
relation.count relation.count
end end
# Find limited amount of non replicated lfs objects.
#
# You can pass a list with `except_registry_ids:` so you can exclude items you
# already scheduled but haven't finished and persisted to the database yet
#
# TODO: Alternative here is to use some sort of window function with a cursor instead
# of simply limiting the query and passing a list of items we don't want
#
# @param [Integer] batch_size used to limit the results returned
# @param [Array<Integer>] except_registry_ids ids that will be ignored from the query
def find_unsynced_lfs_objects(batch_size:, except_registry_ids: [])
relation =
if use_legacy_queries?
legacy_find_unsynced_lfs_objects(except_registry_ids: except_registry_ids)
else
fdw_find_unsynced_lfs_objects(except_registry_ids: except_registry_ids)
end
relation.limit(batch_size)
end
def lfs_objects def lfs_objects
relation = relation =
if selective_sync? if selective_sync?
...@@ -47,29 +68,50 @@ module Geo ...@@ -47,29 +68,50 @@ module Geo
Geo::FileRegistry.lfs_objects.failed Geo::FileRegistry.lfs_objects.failed
end end
#
# FDW accessors
#
def fdw_find_unsynced_lfs_objects(except_registry_ids:)
fdw_table = Geo::Fdw::LfsObject.table_name
# Filter out objects in object storage (this is done in GeoNode#lfs_objects)
Geo::Fdw::LfsObject.joins("LEFT OUTER JOIN file_registry
ON file_registry.file_id = #{fdw_table}.id
AND file_registry.file_type = 'lfs'")
.merge(Geo::Fdw::LfsObject.with_files_stored_locally)
.where(file_registry: { id: nil })
.where.not(id: except_registry_ids)
end
#
# Legacy accessors (non FDW)
#
def legacy_find_synced_lfs_objects def legacy_find_synced_lfs_objects
legacy_find_lfs_objects(find_synced_lfs_objects_registries.pluck(:file_id)) legacy_inner_join_registry_ids(
lfs_objects,
find_synced_lfs_objects_registries.pluck(:file_id),
LfsObject
)
end end
def legacy_find_failed_lfs_objects def legacy_find_failed_lfs_objects
legacy_find_lfs_objects(find_failed_lfs_objects_registries.pluck(:file_id)) legacy_inner_join_registry_ids(
lfs_objects,
find_failed_lfs_objects_registries.pluck(:file_id),
LfsObject
)
end end
def legacy_find_lfs_objects(registry_file_ids) def legacy_find_unsynced_lfs_objects(except_registry_ids:)
return LfsObject.none if registry_file_ids.empty? registry_ids = legacy_pluck_registry_ids(file_types: :lfs, except_registry_ids: except_registry_ids)
lfs_objects = LfsObject.joins(:projects)
.where(projects: { id: current_node.projects })
.with_files_stored_locally
joined_relation = lfs_objects.joins(<<~SQL)
INNER JOIN
(VALUES #{registry_file_ids.map { |id| "(#{id})" }.join(',')})
file_registry(file_id)
ON #{LfsObject.table_name}.id = file_registry.file_id
SQL
joined_relation legacy_left_outer_join_registry_ids(
lfs_objects,
registry_ids,
LfsObject
)
end end
end end
end end
...@@ -7,7 +7,7 @@ module Geo ...@@ -7,7 +7,7 @@ module Geo
def count_synced_project_registries def count_synced_project_registries
relation = relation =
if selective_sync? if selective_sync?
legacy_find_synced_project_registries legacy_find_synced_projects
else else
find_synced_project_registries find_synced_project_registries
end end
...@@ -22,7 +22,7 @@ module Geo ...@@ -22,7 +22,7 @@ module Geo
def find_failed_project_registries(type = nil) def find_failed_project_registries(type = nil)
relation = relation =
if selective_sync? if selective_sync?
legacy_find_filtered_failed_project_registries(type) legacy_find_filtered_failed_projects(type)
else else
find_filtered_failed_project_registries(type) find_filtered_failed_project_registries(type)
end end
...@@ -80,7 +80,7 @@ module Geo ...@@ -80,7 +80,7 @@ module Geo
# @return [ActiveRecord::Relation<Geo::Fdw::Project>] # @return [ActiveRecord::Relation<Geo::Fdw::Project>]
def fdw_find_unsynced_projects def fdw_find_unsynced_projects
Geo::Fdw::Project.joins("LEFT OUTER JOIN project_registry ON project_registry.project_id = #{fdw_table}.id") Geo::Fdw::Project.joins("LEFT OUTER JOIN project_registry ON project_registry.project_id = #{fdw_table}.id")
.where('project_registry.project_id IS NULL') .where(project_registry: { project_id: nil })
end end
# @return [ActiveRecord::Relation<Geo::Fdw::Project>] # @return [ActiveRecord::Relation<Geo::Fdw::Project>]
...@@ -96,61 +96,39 @@ module Geo ...@@ -96,61 +96,39 @@ module Geo
# @return [ActiveRecord::Relation<Project>] list of unsynced projects # @return [ActiveRecord::Relation<Project>] list of unsynced projects
def legacy_find_unsynced_projects def legacy_find_unsynced_projects
registry_project_ids = Geo::ProjectRegistry.pluck(:project_id) legacy_left_outer_join_registry_ids(
return current_node.projects if registry_project_ids.empty? current_node.projects,
Geo::ProjectRegistry.pluck(:project_id),
joined_relation = current_node.projects.joins(<<~SQL) Project
LEFT OUTER JOIN )
(VALUES #{registry_project_ids.map { |id| "(#{id}, 't')" }.join(',')})
project_registry(project_id, registry_present)
ON projects.id = project_registry.project_id
SQL
joined_relation.where(project_registry: { registry_present: [nil, false] })
end end
# @return [ActiveRecord::Relation<Project>] list of projects updated recently # @return [ActiveRecord::Relation<Project>] list of projects updated recently
def legacy_find_projects_updated_recently def legacy_find_projects_updated_recently
legacy_find_projects(Geo::ProjectRegistry.dirty.retry_due.pluck(:project_id)) legacy_inner_join_registry_ids(
end current_node.projects,
Geo::ProjectRegistry.dirty.retry_due.pluck(:project_id),
# @return [ActiveRecord::Relation<Geo::ProjectRegistry>] list of synced projects Project
def legacy_find_synced_project_registries )
legacy_find_project_registries(Geo::ProjectRegistry.synced) end
end
# @return [ActiveRecord::Relation<Project>] list of synced projects
# @return [ActiveRecord::Relation<Geo::ProjectRegistry>] list of projects that sync has failed def legacy_find_synced_projects
def legacy_find_filtered_failed_project_registries(type = nil) legacy_inner_join_registry_ids(
project_registries = find_filtered_failed_project_registries(type) current_node.projects,
legacy_find_project_registries(project_registries) Geo::ProjectRegistry.synced.pluck(:project_id),
end Project
)
# @return [ActiveRecord::Relation<Project>] end
def legacy_find_projects(registry_project_ids)
return Project.none if registry_project_ids.empty? # @return [ActiveRecord::Relation<Project>] list of projects that sync has failed
def legacy_find_filtered_failed_projects(type = nil)
joined_relation = current_node.projects.joins(<<~SQL) legacy_inner_join_registry_ids(
INNER JOIN find_filtered_failed_project_registries(type),
(VALUES #{registry_project_ids.map { |id| "(#{id})" }.join(',')}) current_node.projects.pluck(:id),
project_registry(project_id) Geo::ProjectRegistry,
ON #{Project.table_name}.id = project_registry.project_id foreign_key: :project_id
SQL )
joined_relation
end
# @return [ActiveRecord::Relation<Geo::ProjectRegistry>]
def legacy_find_project_registries(project_registries)
return Geo::ProjectRegistry.none if project_registries.empty?
joined_relation = project_registries.joins(<<~SQL)
INNER JOIN
(VALUES #{current_node.projects.pluck(:id).map { |id| "(#{id})" }.join(',')})
projects(id)
ON #{Geo::ProjectRegistry.table_name}.project_id = projects.id
SQL
joined_relation
end end
end end
end end
...@@ -15,5 +15,31 @@ module Geo ...@@ -15,5 +15,31 @@ module Geo
# queries, so we fallback to the legacy version for now. # queries, so we fallback to the legacy version for now.
!Gitlab::Geo.fdw? || selective_sync? !Gitlab::Geo.fdw? || selective_sync?
end end
def legacy_inner_join_registry_ids(objects, registry_ids, klass, foreign_key: :id)
return klass.none if registry_ids.empty?
joined_relation = objects.joins(<<~SQL)
INNER JOIN
(VALUES #{registry_ids.map { |id| "(#{id})" }.join(',')})
registry(id)
ON #{klass.table_name}.#{foreign_key} = registry.id
SQL
joined_relation
end
def legacy_left_outer_join_registry_ids(objects, registry_ids, klass)
return objects if registry_ids.empty?
joined_relation = objects.joins(<<~SQL)
LEFT OUTER JOIN
(VALUES #{registry_ids.map { |id| "(#{id}, 't')" }.join(',')})
registry(id, registry_present)
ON #{klass.table_name}.id = registry.id
SQL
joined_relation.where(registry: { registry_present: [nil, false] })
end
end end
end end
...@@ -7,6 +7,10 @@ module EE ...@@ -7,6 +7,10 @@ module EE
module Build module Build
extend ActiveSupport::Concern extend ActiveSupport::Concern
CODEQUALITY_FILE = 'codeclimate.json'.freeze
SAST_FILE = 'gl-sast-report.json'.freeze
PERFORMANCE_FILE = 'performance.json'.freeze
included do included do
scope :codequality, ->() { where(name: %w[codequality codeclimate]) } scope :codequality, ->() { where(name: %w[codequality codeclimate]) }
scope :performance, ->() { where(name: %w[performance deploy]) } scope :performance, ->() { where(name: %w[performance deploy]) }
...@@ -27,17 +31,21 @@ module EE ...@@ -27,17 +31,21 @@ module EE
end end
def has_codeclimate_json? def has_codeclimate_json?
options.dig(:artifacts, :paths) == ['codeclimate.json'] && has_artifact?(CODEQUALITY_FILE)
artifacts_metadata?
end end
def has_performance_json? def has_performance_json?
options.dig(:artifacts, :paths) == ['performance.json'] && has_artifact?(PERFORMANCE_FILE)
artifacts_metadata?
end end
def has_sast_json? def has_sast_json?
options.dig(:artifacts, :paths) == ['gl-sast-report.json'] && has_artifact?(SAST_FILE)
end
private
def has_artifact?(name)
options.dig(:artifacts, :paths) == [name] &&
artifacts_metadata? artifacts_metadata?
end end
end end
......
...@@ -7,7 +7,7 @@ module EE ...@@ -7,7 +7,7 @@ module EE
expose :head_path, if: -> (mr, _) { can?(current_user, :read_build, mr.head_codeclimate_artifact) } do |merge_request| expose :head_path, if: -> (mr, _) { can?(current_user, :read_build, mr.head_codeclimate_artifact) } do |merge_request|
raw_project_build_artifacts_url(merge_request.source_project, raw_project_build_artifacts_url(merge_request.source_project,
merge_request.head_codeclimate_artifact, merge_request.head_codeclimate_artifact,
path: 'codeclimate.json') path: Ci::Build::CODEQUALITY_FILE)
end end
expose :head_blob_path, if: -> (mr, _) { mr.head_pipeline_sha } do |merge_request| expose :head_blob_path, if: -> (mr, _) { mr.head_pipeline_sha } do |merge_request|
...@@ -17,7 +17,7 @@ module EE ...@@ -17,7 +17,7 @@ module EE
expose :base_path, if: -> (mr, _) { can?(current_user, :read_build, mr.base_codeclimate_artifact) } do |merge_request| expose :base_path, if: -> (mr, _) { can?(current_user, :read_build, mr.base_codeclimate_artifact) } do |merge_request|
raw_project_build_artifacts_url(merge_request.target_project, raw_project_build_artifacts_url(merge_request.target_project,
merge_request.base_codeclimate_artifact, merge_request.base_codeclimate_artifact,
path: 'codeclimate.json') path: Ci::Build::CODEQUALITY_FILE)
end end
expose :base_blob_path, if: -> (mr, _) { mr.base_pipeline_sha } do |merge_request| expose :base_blob_path, if: -> (mr, _) { mr.base_pipeline_sha } do |merge_request|
...@@ -29,13 +29,13 @@ module EE ...@@ -29,13 +29,13 @@ module EE
expose :head_path, if: -> (mr, _) { can?(current_user, :read_build, mr.head_performance_artifact) } do |merge_request| expose :head_path, if: -> (mr, _) { can?(current_user, :read_build, mr.head_performance_artifact) } do |merge_request|
raw_project_build_artifacts_url(merge_request.source_project, raw_project_build_artifacts_url(merge_request.source_project,
merge_request.head_performance_artifact, merge_request.head_performance_artifact,
path: 'performance.json') path: Ci::Build::PERFORMANCE_FILE)
end end
expose :base_path, if: -> (mr, _) { can?(current_user, :read_build, mr.base_performance_artifact) } do |merge_request| expose :base_path, if: -> (mr, _) { can?(current_user, :read_build, mr.base_performance_artifact) } do |merge_request|
raw_project_build_artifacts_url(merge_request.target_project, raw_project_build_artifacts_url(merge_request.target_project,
merge_request.base_performance_artifact, merge_request.base_performance_artifact,
path: 'performance.json') path: Ci::Build::PERFORMANCE_FILE)
end end
end end
...@@ -43,7 +43,7 @@ module EE ...@@ -43,7 +43,7 @@ module EE
expose :path do |merge_request| expose :path do |merge_request|
raw_project_build_artifacts_url(merge_request.source_project, raw_project_build_artifacts_url(merge_request.source_project,
merge_request.sast_artifact, merge_request.sast_artifact,
path: 'gl-sast-report.json') path: Ci::Build::SAST_FILE)
end end
expose :blob_path, if: -> (mr, _) { mr.head_pipeline_sha } do |merge_request| expose :blob_path, if: -> (mr, _) { mr.head_pipeline_sha } do |merge_request|
......
.nav-controls
- if project.feature_available?(:multiple_clusters)
= link_to s_("ClusterIntegration|Add cluster"), new_project_cluster_path(project), class: "btn btn-success btn-add-cluster has-tooltip js-add-cluster"
- else
= link_to s_("ClusterIntegration|Add cluster"), new_project_cluster_path(project), class: "btn btn-success btn-add-cluster disabled has-tooltip js-add-cluster", title: s_("ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate")
...@@ -27,13 +27,6 @@ describe Projects::ClustersController do ...@@ -27,13 +27,6 @@ describe Projects::ClustersController do
expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster]) expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
end end
it 'assigns counters to correct values' do
go
expect(assigns(:active_count)).to eq(1)
expect(assigns(:inactive_count)).to eq(1)
end
context 'when page is specified' do context 'when page is specified' do
let(:last_page) { project.clusters.page.total_pages } let(:last_page) { project.clusters.page.total_pages }
...@@ -48,20 +41,6 @@ describe Projects::ClustersController do ...@@ -48,20 +41,6 @@ describe Projects::ClustersController do
expect(assigns(:clusters).current_page).to eq(last_page) expect(assigns(:clusters).current_page).to eq(last_page)
end end
end end
context 'when only enabled clusters are requested' do
it 'returns only enabled clusters' do
get :index, namespace_id: project.namespace, project_id: project, scope: 'active'
expect(assigns(:clusters)).to all(have_attributes(enabled: true))
end
end
context 'when only disabled clusters are requested' do
it 'returns only disabled clusters' do
get :index, namespace_id: project.namespace, project_id: project, scope: 'inactive'
expect(assigns(:clusters)).to all(have_attributes(enabled: false))
end
end
end end
context 'when project does not have a cluster' do context 'when project does not have a cluster' do
...@@ -74,13 +53,6 @@ describe Projects::ClustersController do ...@@ -74,13 +53,6 @@ describe Projects::ClustersController do
expect(response).to render_template(:index, partial: :empty_state) expect(response).to render_template(:index, partial: :empty_state)
expect(assigns(:clusters)).to eq([]) expect(assigns(:clusters)).to eq([])
end end
it 'assigns counters to zero' do
go
expect(assigns(:active_count)).to eq(0)
expect(assigns(:inactive_count)).to eq(0)
end
end end
end end
......
require 'spec_helper'
feature 'Clusters', :js do
include GoogleApi::CloudPlatformHelpers
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
gitlab_sign_in(user)
end
context 'when user has a cluster and visits cluster index page' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
before do
visit project_clusters_path(project)
end
context 'when license has multiple clusters feature' do
before do
allow_any_instance_of(EE::Project).to receive(:feature_available?).with(:multiple_clusters).and_return(true)
end
it 'user sees a add cluster button ' do
expect(page).to have_selector('.js-add-cluster')
end
end
context 'when license does not have multiple clusters feature' do
before do
allow_any_instance_of(EE::Project).to receive(:feature_available?).with(:multiple_clusters).and_return(false)
end
it 'user sees a disabled add cluster button ' do
expect(page).to have_selector('.js-add-cluster.disabled')
end
end
end
end
...@@ -11,10 +11,10 @@ describe Geo::AttachmentRegistryFinder, :geo do ...@@ -11,10 +11,10 @@ describe Geo::AttachmentRegistryFinder, :geo do
let(:synced_project) { create(:project, group: synced_group) } let(:synced_project) { create(:project, group: synced_group) }
let(:unsynced_project) { create(:project, group: unsynced_group) } let(:unsynced_project) { create(:project, group: unsynced_group) }
let(:upload_1) { create(:upload, model: synced_group) } let!(:upload_1) { create(:upload, model: synced_group) }
let(:upload_2) { create(:upload, model: unsynced_group) } let!(:upload_2) { create(:upload, model: unsynced_group) }
let(:upload_3) { create(:upload, :issuable_upload, model: synced_project) } let!(:upload_3) { create(:upload, :issuable_upload, model: synced_project) }
let(:upload_4) { create(:upload, model: unsynced_project) } let!(:upload_4) { create(:upload, model: unsynced_project) }
let(:upload_5) { create(:upload, model: synced_project) } let(:upload_5) { create(:upload, model: synced_project) }
let(:upload_6) { create(:upload, :personal_snippet) } let(:upload_6) { create(:upload, :personal_snippet) }
let(:upload_7) { create(:upload, model: synced_subgroup) } let(:upload_7) { create(:upload, model: synced_subgroup) }
...@@ -112,6 +112,30 @@ describe Geo::AttachmentRegistryFinder, :geo do ...@@ -112,6 +112,30 @@ describe Geo::AttachmentRegistryFinder, :geo do
end end
end end
end end
describe '#find_unsynced_attachments' do
it 'delegates to #fdw_find_unsynced_attachments' do
expect(subject).to receive(:fdw_find_unsynced_attachments).and_call_original
subject.find_unsynced_attachments(batch_size: 10)
end
it 'returns uploads without an entry on the tracking database' do
create(:geo_file_registry, :avatar, file_id: upload_1.id, success: true)
uploads = subject.find_unsynced_attachments(batch_size: 10)
expect(uploads.map(&:id)).to match_array([upload_2.id, upload_3.id, upload_4.id])
end
it 'excludes uploads without an entry on the tracking database' do
create(:geo_file_registry, :avatar, file_id: upload_1.id, success: true)
uploads = subject.find_unsynced_attachments(batch_size: 10, except_registry_ids: [upload_2.id])
expect(uploads.map(&:id)).to match_array([upload_3.id, upload_4.id])
end
end
end end
context 'Legacy' do context 'Legacy' do
...@@ -198,5 +222,29 @@ describe Geo::AttachmentRegistryFinder, :geo do ...@@ -198,5 +222,29 @@ describe Geo::AttachmentRegistryFinder, :geo do
end end
end end
end end
describe '#find_unsynced_attachments' do
it 'delegates to #legacy_find_unsynced_attachments' do
expect(subject).to receive(:legacy_find_unsynced_attachments).and_call_original
subject.find_unsynced_attachments(batch_size: 10)
end
it 'returns LFS objects without an entry on the tracking database' do
create(:geo_file_registry, :avatar, file_id: upload_1.id, success: true)
uploads = subject.find_unsynced_attachments(batch_size: 10)
expect(uploads).to match_array([upload_2, upload_3, upload_4])
end
it 'excludes uploads without an entry on the tracking database' do
create(:geo_file_registry, :avatar, file_id: upload_1.id, success: true)
uploads = subject.find_unsynced_attachments(batch_size: 10, except_registry_ids: [upload_2.id])
expect(uploads).to match_array([upload_3, upload_4])
end
end
end end
end end
require 'spec_helper'
describe Geo::FileRegistryFinder, :geo do
include ::EE::GeoHelpers
let(:secondary) { create(:geo_node) }
subject { described_class.new(current_node: secondary) }
before do
stub_current_geo_node(secondary)
end
describe '#find_failed_file_registries' do
it 'returs uploads that sync has failed' do
failed_lfs_registry = create(:geo_file_registry, :lfs, :with_file, success: false)
failed_file_upload = create(:geo_file_registry, :with_file, success: false)
failed_issuable_upload = create(:geo_file_registry, :with_file, success: false)
create(:geo_file_registry, :lfs, :with_file, success: true)
create(:geo_file_registry, :with_file, success: true)
uploads = subject.find_failed_file_registries(batch_size: 10)
expect(uploads).to match_array([failed_lfs_registry, failed_file_upload, failed_issuable_upload])
end
end
end
...@@ -7,9 +7,11 @@ describe Geo::LfsObjectRegistryFinder, :geo do ...@@ -7,9 +7,11 @@ describe Geo::LfsObjectRegistryFinder, :geo do
let(:synced_group) { create(:group) } let(:synced_group) { create(:group) }
let(:synced_project) { create(:project, group: synced_group) } let(:synced_project) { create(:project, group: synced_group) }
let(:unsynced_project) { create(:project) } let(:unsynced_project) { create(:project) }
let(:lfs_object_1) { create(:lfs_object) }
let(:lfs_object_2) { create(:lfs_object) } let!(:lfs_object_1) { create(:lfs_object) }
let(:lfs_object_3) { create(:lfs_object) } let!(:lfs_object_2) { create(:lfs_object) }
let!(:lfs_object_3) { create(:lfs_object) }
let!(:lfs_object_4) { create(:lfs_object) }
subject { described_class.new(current_node: secondary) } subject { described_class.new(current_node: secondary) }
...@@ -100,4 +102,70 @@ describe Geo::LfsObjectRegistryFinder, :geo do ...@@ -100,4 +102,70 @@ describe Geo::LfsObjectRegistryFinder, :geo do
end end
end end
end end
# Disable transactions via :delete method because a foreign table
# can't see changes inside a transaction of a different connection.
context 'FDW', :delete do
before do
skip('FDW is not configured') if Gitlab::Database.postgresql? && !Gitlab::Geo.fdw?
end
describe '#find_unsynced_lfs_objects' do
it 'delegates to #fdw_find_unsynced_lfs_objects' do
expect(subject).to receive(:fdw_find_unsynced_lfs_objects).and_call_original
subject.find_unsynced_lfs_objects(batch_size: 10)
end
it 'returns LFS objects without an entry on the tracking database' do
create(:geo_file_registry, :lfs, file_id: lfs_object_1.id, success: true)
create(:geo_file_registry, :lfs, file_id: lfs_object_3.id, success: false)
lfs_objects = subject.find_unsynced_lfs_objects(batch_size: 10)
expect(lfs_objects.map(&:id)).to match_array([lfs_object_2.id, lfs_object_4.id])
end
it 'excludes LFS objects without an entry on the tracking database' do
create(:geo_file_registry, :lfs, file_id: lfs_object_1.id, success: true)
create(:geo_file_registry, :lfs, file_id: lfs_object_3.id, success: false)
lfs_objects = subject.find_unsynced_lfs_objects(batch_size: 10, except_registry_ids: [lfs_object_2.id])
expect(lfs_objects.map(&:id)).to match_array([lfs_object_4.id])
end
end
end
context 'Legacy' do
before do
allow(Gitlab::Geo).to receive(:fdw?).and_return(false)
end
describe '#find_unsynced_lfs_objects' do
it 'delegates to #legacy_find_unsynced_lfs_objects' do
expect(subject).to receive(:legacy_find_unsynced_lfs_objects).and_call_original
subject.find_unsynced_lfs_objects(batch_size: 10)
end
it 'returns LFS objects without an entry on the tracking database' do
create(:geo_file_registry, :lfs, file_id: lfs_object_1.id, success: true)
create(:geo_file_registry, :lfs, file_id: lfs_object_3.id, success: false)
lfs_objects = subject.find_unsynced_lfs_objects(batch_size: 10)
expect(lfs_objects).to match_array([lfs_object_2, lfs_object_4])
end
it 'excludes LFS objects without an entry on the tracking database' do
create(:geo_file_registry, :lfs, file_id: lfs_object_1.id, success: true)
create(:geo_file_registry, :lfs, file_id: lfs_object_3.id, success: false)
lfs_objects = subject.find_unsynced_lfs_objects(batch_size: 10, except_registry_ids: [lfs_object_2.id])
expect(lfs_objects).to match_array([lfs_object_4])
end
end
end
end end
...@@ -37,8 +37,8 @@ describe Geo::ProjectRegistryFinder, :geo do ...@@ -37,8 +37,8 @@ describe Geo::ProjectRegistryFinder, :geo do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update_attribute(:namespaces, [synced_group])
end end
it 'delegates to #legacy_find_synced_project_registries' do it 'delegates to #legacy_find_synced_projects' do
expect(subject).to receive(:legacy_find_synced_project_registries).and_call_original expect(subject).to receive(:legacy_find_synced_projects).and_call_original
subject.count_synced_project_registries subject.count_synced_project_registries
end end
...@@ -128,8 +128,8 @@ describe Geo::ProjectRegistryFinder, :geo do ...@@ -128,8 +128,8 @@ describe Geo::ProjectRegistryFinder, :geo do
secondary.update_attribute(:namespaces, [synced_group]) secondary.update_attribute(:namespaces, [synced_group])
end end
it 'delegates to #legacy_find_filtered_failed_project_registries' do it 'delegates to #legacy_find_filtered_failed_projects' do
expect(subject).to receive(:legacy_find_filtered_failed_project_registries).and_call_original expect(subject).to receive(:legacy_find_filtered_failed_projects).and_call_original
subject.find_failed_project_registries subject.find_failed_project_registries
end end
......
...@@ -128,105 +128,43 @@ describe Ci::Build do ...@@ -128,105 +128,43 @@ describe Ci::Build do
end end
end end
describe '#has_codeclimate_json?' do ARTIFACTS_METHODS = {
context 'valid build' do has_codeclimate_json?: Ci::Build::CODEQUALITY_FILE,
let!(:build) do has_performance_json?: Ci::Build::PERFORMANCE_FILE,
create( has_sast_json?: Ci::Build::SAST_FILE
:ci_build, }.freeze
:artifacts,
name: 'codequality', ARTIFACTS_METHODS.each do |method, filename|
pipeline: pipeline, describe "##{method}" do
options: { context 'valid build' do
artifacts: { let!(:build) do
paths: ['codeclimate.json'] create(
:ci_build,
:artifacts,
pipeline: pipeline,
options: {
artifacts: {
paths: [filename]
}
} }
} )
) end
end
it { expect(build.has_codeclimate_json?).to be_truthy }
end
context 'invalid build' do
let!(:build) do
create(
:ci_build,
:artifacts,
name: 'codequality',
pipeline: pipeline,
options: {}
)
end
it { expect(build.has_codeclimate_json?).to be_falsey }
end
end
describe '#has_performance_json?' do
context 'valid build' do
let!(:build) do
create(
:ci_build,
:artifacts,
name: 'performance',
pipeline: pipeline,
options: {
artifacts: {
paths: ['performance.json']
}
}
)
end
it { expect(build.has_performance_json?).to be_truthy }
end
context 'invalid build' do
let!(:build) do
create(
:ci_build,
:artifacts,
name: 'performance',
pipeline: pipeline,
options: {}
)
end
it { expect(build.has_performance_json?).to be_falsey }
end
end
describe '#has_sast_json?' do it { expect(build.send(method)).to be_truthy }
context 'valid build' do
let!(:build) do
create(
:ci_build,
:artifacts,
name: 'sast',
pipeline: pipeline,
options: {
artifacts: {
paths: ['gl-sast-report.json']
}
}
)
end end
it { expect(build.has_sast_json?).to be_truthy } context 'invalid build' do
end let!(:build) do
create(
:ci_build,
:artifacts,
pipeline: pipeline,
options: {}
)
end
context 'invalid build' do it { expect(build.send(method)).to be_falsey }
let!(:build) do
create(
:ci_build,
:artifacts,
name: 'sast',
pipeline: pipeline,
options: {}
)
end end
it { expect(build.has_sast_json?).to be_falsey }
end end
end end
end end
...@@ -35,37 +35,6 @@ feature 'Clusters', :js do ...@@ -35,37 +35,6 @@ feature 'Clusters', :js do
expect(page).to have_selector('.gl-responsive-table-row', count: 2) expect(page).to have_selector('.gl-responsive-table-row', count: 2)
end end
context 'when license has multiple clusters feature' do
before do
allow_any_instance_of(EE::Project).to receive(:feature_available?).with(:multiple_clusters).and_return(true)
end
it 'user sees a add cluster button ' do
expect(page).to have_selector('.js-add-cluster')
end
end
context 'when license does not have multiple clusters feature' do
before do
allow_any_instance_of(EE::Project).to receive(:feature_available?).with(:multiple_clusters).and_return(false)
end
it 'user sees a disabled add cluster button ' do
expect(page).to have_selector('.js-add-cluster.disabled')
end
end
it 'user sees navigation tabs' do
expect(page.find('.js-active-tab').text).to include('Active')
expect(page.find('.js-active-tab .badge').text).to include('1')
expect(page.find('.js-inactive-tab').text).to include('Inactive')
expect(page.find('.js-inactive-tab .badge').text).to include('0')
expect(page.find('.js-all-tab').text).to include('All')
expect(page.find('.js-all-tab .badge').text).to include('1')
end
context 'inline update of cluster' do context 'inline update of cluster' do
it 'user can update cluster' do it 'user can update cluster' do
expect(page).to have_selector('.js-toggle-cluster-list') expect(page).to have_selector('.js-toggle-cluster-list')
......
...@@ -2295,7 +2295,24 @@ describe MergeRequest do ...@@ -2295,7 +2295,24 @@ describe MergeRequest do
end end
end end
<<<<<<< HEAD
it_behaves_like 'throttled touch' do it_behaves_like 'throttled touch' do
subject { create(:merge_request, updated_at: 1.hour.ago) } subject { create(:merge_request, updated_at: 1.hour.ago) }
=======
context 'state machine transitions' do
describe '#unlock_mr' do
subject { create(:merge_request, state: 'locked', merge_jid: 123) }
it 'updates merge request head pipeline and sets merge_jid to nil' do
pipeline = create(:ci_empty_pipeline, project: subject.project, ref: subject.source_branch, sha: subject.source_branch_sha)
subject.unlock_mr
subject.reload
expect(subject.head_pipeline).to eq(pipeline)
expect(subject.merge_jid).to be_nil
end
end
>>>>>>> 025bdb21e8f30282fdf3a6675bf9d3de11e6235d
end end
end end
...@@ -64,6 +64,18 @@ describe Ci::CreatePipelineService do ...@@ -64,6 +64,18 @@ describe Ci::CreatePipelineService do
create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project) create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
end end
context 'when related merge request is already merged' do
let!(:merged_merge_request) do
create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project, state: 'merged')
end
it 'does not schedule update head pipeline job' do
expect(UpdateHeadPipelineForMergeRequestWorker).not_to receive(:perform_async).with(merged_merge_request.id)
execute_service
end
end
context 'when the head pipeline sha equals merge request sha' do context 'when the head pipeline sha equals merge request sha' do
it 'updates head pipeline of each merge request' do it 'updates head pipeline of each merge request' do
merge_request_1 merge_request_1
...@@ -77,13 +89,13 @@ describe Ci::CreatePipelineService do ...@@ -77,13 +89,13 @@ describe Ci::CreatePipelineService do
end end
context 'when the head pipeline sha does not equal merge request sha' do context 'when the head pipeline sha does not equal merge request sha' do
it 'raises the ArgumentError error from worker and does not update the head piepeline of MRs' do it 'does not update the head piepeline of MRs' do
merge_request_1 merge_request_1
merge_request_2 merge_request_2
allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(true) allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(true)
expect { execute_service(after: 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }.to raise_error(ArgumentError) expect { execute_service(after: 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }.not_to raise_error
last_pipeline = Ci::Pipeline.last last_pipeline = Ci::Pipeline.last
......
...@@ -14,7 +14,6 @@ describe StuckMergeJobsWorker do ...@@ -14,7 +14,6 @@ describe StuckMergeJobsWorker do
mr_with_sha.reload mr_with_sha.reload
mr_without_sha.reload mr_without_sha.reload
expect(mr_with_sha).to be_merged expect(mr_with_sha).to be_merged
expect(mr_without_sha).to be_opened expect(mr_without_sha).to be_opened
expect(mr_with_sha.merge_jid).to be_present expect(mr_with_sha.merge_jid).to be_present
...@@ -24,10 +23,13 @@ describe StuckMergeJobsWorker do ...@@ -24,10 +23,13 @@ describe StuckMergeJobsWorker do
it 'updates merge request to opened when locked but has not been merged' do it 'updates merge request to opened when locked but has not been merged' do
allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123)) allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123))
merge_request = create(:merge_request, :locked, merge_jid: '123', state: :locked) merge_request = create(:merge_request, :locked, merge_jid: '123', state: :locked)
pipeline = create(:ci_empty_pipeline, project: merge_request.project, ref: merge_request.source_branch, sha: merge_request.source_branch_sha)
worker.perform worker.perform
expect(merge_request.reload).to be_opened merge_request.reload
expect(merge_request).to be_opened
expect(merge_request.head_pipeline).to eq(pipeline)
end end
it 'logs updated stuck merge job ids' do it 'logs updated stuck merge job ids' do
......
...@@ -22,7 +22,7 @@ describe UpdateHeadPipelineForMergeRequestWorker do ...@@ -22,7 +22,7 @@ describe UpdateHeadPipelineForMergeRequestWorker do
end end
it 'does not update head_pipeline_id' do it 'does not update head_pipeline_id' do
expect { subject.perform(merge_request.id) }.to raise_error(ArgumentError) expect { subject.perform(merge_request.id) }.not_to raise_error
expect(merge_request.reload.head_pipeline_id).to eq(nil) expect(merge_request.reload.head_pipeline_id).to eq(nil)
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