Commit a091e24d authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into ce-to-ee-2017-11-30

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents 4b87f081 7afa3f47
Please view this file on the master branch, on stable branches it's out of date.
## 10.2.3 (2017-11-30)
### Fixed (5 changes)
- Fix viewing default push rules on a Geo secondary. !3559
- Disable autocomplete for epics.
- Fix epic fullscreen editing.
- Fix tasklist for epics.
- Fix Geo wiki sync error not increasing retry count.
## 10.2.2 (2017-11-23)
### Fixed (6 changes)
......
......@@ -2,6 +2,25 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.2.3 (2017-11-30)
### Fixed (7 changes)
- Fix hashed storage for Import/Export uploads. !15482
- Ensure that rake gitlab:cleanup:repos task does not mess with hashed repositories. !15520
- Ensure that rake gitlab:cleanup:dirs task does not mess with hashed repositories. !15600
- Fix WIP system note not being created.
- Fix link text from group context.
- Fix defaults for MR states and merge statuses.
- Fix pulling and pushing using a personal access token with the sudo scope.
### Performance (3 changes)
- Drastically improve project search performance by no longer searching namespace name.
- Reuse authors when rendering event Atom feeds.
- Optimise StuckCiJobsWorker using cheap SQL query outside, and expensive inside.
## 10.2.2 (2017-11-23)
### Fixed (5 changes)
......
......@@ -22,7 +22,7 @@
noteType: constants.COMMENT,
// Can't use mapGetters,
// this needs to be in the data object because it belongs to the state
issueState: this.$store.getters.getIssueData.state,
issueState: this.$store.getters.getNoteableData.state,
isSubmitting: false,
isSubmitButtonDisabled: true,
};
......@@ -46,7 +46,7 @@
...mapGetters([
'getCurrentUserLastNote',
'getUserData',
'getIssueData',
'getNoteableData',
'getNotesData',
]),
isLoggedIn() {
......@@ -59,7 +59,7 @@
return this.issueState === constants.OPENED || this.issueState === constants.REOPENED;
},
canCreateNote() {
return this.getIssueData.current_user.can_create_note;
return this.getNoteableData.current_user.can_create_note;
},
issueActionButtonTitle() {
if (this.note.length) {
......@@ -85,16 +85,16 @@
return this.getNotesData.quickActionsDocsPath;
},
markdownPreviewPath() {
return this.getIssueData.preview_note_path;
return this.getNoteableData.preview_note_path;
},
author() {
return this.getUserData;
},
canUpdateIssue() {
return this.getIssueData.current_user.can_update;
return this.getNoteableData.current_user.can_update;
},
endpoint() {
return this.getIssueData.create_note_path;
return this.getNoteableData.create_note_path;
},
},
methods: {
......@@ -119,7 +119,7 @@
data: {
note: {
noteable_type: constants.NOTEABLE_TYPE,
noteable_id: this.getIssueData.id,
noteable_id: this.getNoteableData.id,
note: this.note,
},
},
......@@ -207,7 +207,7 @@
},
initAutoSave() {
if (this.isLoggedIn) {
this.autosave = new Autosave($(this.$refs.textarea), ['Note', 'Issue', this.getIssueData.id], 'issue');
this.autosave = new Autosave($(this.$refs.textarea), ['Note', 'Issue', this.getNoteableData.id], 'issue');
}
},
initTaskList() {
......@@ -266,9 +266,9 @@
<div class="error-alert"></div>
<issue-warning
v-if="hasWarning(getIssueData)"
:is-locked="isLocked(getIssueData)"
:is-confidential="isConfidential(getIssueData)"
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
/>
<markdown-field
......
......@@ -41,7 +41,7 @@
],
computed: {
...mapGetters([
'getIssueData',
'getNoteableData',
]),
discussion() {
return this.note.notes[0];
......@@ -50,10 +50,10 @@
return this.discussion.author;
},
canReply() {
return this.getIssueData.current_user.can_create_note;
return this.getNoteableData.current_user.can_create_note;
},
newNotePath() {
return this.getIssueData.create_note_path;
return this.getNoteableData.create_note_path;
},
lastUpdatedBy() {
const { notes } = this.note;
......
......@@ -46,8 +46,8 @@
computed: {
...mapGetters([
'getDiscussionLastNote',
'getIssueData',
'getIssueDataByProp',
'getNoteableData',
'getNoteableDataByProp',
'getNotesDataByProp',
'getUserDataByProp',
]),
......@@ -55,7 +55,7 @@
return `#note_${this.noteId}`;
},
markdownPreviewPath() {
return this.getIssueDataByProp('preview_note_path');
return this.getNoteableDataByProp('preview_note_path');
},
markdownDocsPath() {
return this.getNotesDataByProp('markdownDocsPath');
......@@ -129,9 +129,9 @@
class="edit-note common-note-form js-quick-submit gfm-form">
<issue-warning
v-if="hasWarning(getIssueData)"
:is-locked="isLocked(getIssueData)"
:is-confidential="isConfidential(getIssueData)"
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
/>
<markdown-field
......
......@@ -14,7 +14,7 @@
export default {
name: 'issueNotesApp',
props: {
issueData: {
noteableData: {
type: Object,
required: true,
},
......@@ -56,7 +56,7 @@
actionToggleAward: 'toggleAward',
scrollToNoteIfNeeded: 'scrollToNoteIfNeeded',
setNotesData: 'setNotesData',
setIssueData: 'setIssueData',
setNoteableData: 'setNoteableData',
setUserData: 'setUserData',
setLastFetchedAt: 'setLastFetchedAt',
setTargetNoteHash: 'setTargetNoteHash',
......@@ -106,7 +106,7 @@
},
created() {
this.setNotesData(this.notesData);
this.setIssueData(this.issueData);
this.setNoteableData(this.noteableData);
this.setUserData(this.userData);
},
mounted() {
......
......@@ -10,7 +10,7 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
const notesDataset = document.getElementById('js-vue-notes').dataset;
return {
issueData: JSON.parse(notesDataset.issueData),
noteableData: JSON.parse(notesDataset.noteableData),
currentUserData: JSON.parse(notesDataset.currentUserData),
notesData: {
lastFetchedAt: notesDataset.lastFetchedAt,
......@@ -26,7 +26,7 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
render(createElement) {
return createElement('issue-notes-app', {
props: {
issueData: this.issueData,
noteableData: this.noteableData,
notesData: this.notesData,
userData: this.currentUserData,
},
......
......@@ -12,7 +12,7 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
let eTagPoll;
export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
export const setIssueData = ({ commit }, data) => commit(types.SET_ISSUE_DATA, data);
export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITIAL_NOTES, data);
......
......@@ -6,8 +6,8 @@ export const targetNoteHash = state => state.targetNoteHash;
export const getNotesData = state => state.notesData;
export const getNotesDataByProp = state => prop => state.notesData[prop];
export const getIssueData = state => state.issueData;
export const getIssueDataByProp = state => prop => state.issueData[prop];
export const getNoteableData = state => state.noteableData;
export const getNoteableDataByProp = state => prop => state.noteableData[prop];
export const getUserData = state => state.userData || {};
export const getUserDataByProp = state => prop => state.userData && state.userData[prop];
......
......@@ -15,7 +15,7 @@ export default new Vuex.Store({
// holds endpoints and permissions provided through haml
notesData: {},
userData: {},
issueData: {},
noteableData: {},
},
actions,
getters,
......
......@@ -3,7 +3,7 @@ export const ADD_NEW_REPLY_TO_DISCUSSION = 'ADD_NEW_REPLY_TO_DISCUSSION';
export const DELETE_NOTE = 'DELETE_NOTE';
export const REMOVE_PLACEHOLDER_NOTES = 'REMOVE_PLACEHOLDER_NOTES';
export const SET_NOTES_DATA = 'SET_NOTES_DATA';
export const SET_ISSUE_DATA = 'SET_ISSUE_DATA';
export const SET_NOTEABLE_DATA = 'SET_NOTEABLE_DATA';
export const SET_USER_DATA = 'SET_USER_DATA';
export const SET_INITIAL_NOTES = 'SET_INITIAL_NOTES';
export const SET_LAST_FETCHED_AT = 'SET_LAST_FETCHED_AT';
......
......@@ -66,8 +66,8 @@ export default {
Object.assign(state, { notesData: data });
},
[types.SET_ISSUE_DATA](state, data) {
Object.assign(state, { issueData: data });
[types.SET_NOTEABLE_DATA](state, data) {
Object.assign(state, { noteableData: data });
},
[types.SET_USER_DATA](state, data) {
......
......@@ -5,6 +5,11 @@ module Clusters
include Gitlab::Kubernetes
include ReactiveCaching
<<<<<<< HEAD
=======
prepend EE::KubernetesService
>>>>>>> origin/master
self.table_name = 'cluster_platforms_kubernetes'
self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.id] }
......@@ -85,6 +90,19 @@ module Clusters
# network access
def calculate_reactive_cache
return unless enabled? && project && !project.pending_delete?
<<<<<<< HEAD
# We may want to cache extra things in the future
{ pods: read_pods }
end
def kubeclient
@kubeclient ||= build_kubeclient!
end
private
=======
# We may want to cache extra things in the future
{ pods: read_pods }
......@@ -96,12 +114,83 @@ module Clusters
private
>>>>>>> origin/master
def kubeconfig
to_kubeconfig(
url: api_url,
namespace: actual_namespace,
token: token,
ca_pem: ca_pem)
<<<<<<< HEAD
end
def default_namespace
return unless project
slug = "#{project.path}-#{project.id}".downcase
slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
end
def build_kubeclient!(api_path: 'api', api_version: 'v1')
raise "Incomplete settings" unless api_url && actual_namespace
unless (username && password) || token
raise "Either username/password or token is required to access API"
end
::Kubeclient::Client.new(
join_api_url(api_path),
api_version,
auth_options: kubeclient_auth_options,
ssl_options: kubeclient_ssl_options,
http_proxy_uri: ENV['http_proxy']
)
end
# Returns a hash of all pods in the namespace
def read_pods
kubeclient = build_kubeclient!
kubeclient.get_pods(namespace: actual_namespace).as_json
rescue KubeException => err
raise err unless err.error_code == 404
[]
end
def kubeclient_ssl_options
opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
if ca_pem.present?
opts[:cert_store] = OpenSSL::X509::Store.new
opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
end
opts
end
def kubeclient_auth_options
{ bearer_token: token }
end
def join_api_url(api_path)
url = URI.parse(api_url)
prefix = url.path.sub(%r{/+\z}, '')
url.path = [prefix, api_path].join("/")
url.to_s
end
def terminal_auth
{
token: token,
ca_pem: ca_pem,
max_session_time: current_application_settings.terminal_max_session_time
}
end
=======
end
def default_namespace
......@@ -170,6 +259,7 @@ module Clusters
}
end
>>>>>>> origin/master
def enforce_namespace_to_lower_case
self.namespace = self.namespace&.downcase
end
......
......@@ -146,7 +146,7 @@ class Environment < ActiveRecord::Base
end
def rollout_status
project.deployment_platform.rollout_status(self) if deployment_service_ready?
project.deployment_service.rollout_status(self) if deployment_service_ready?
end
def has_metrics?
......
......@@ -23,7 +23,7 @@ class EnvironmentEntity < Grape::Entity
stop_project_environment_path(environment.project, environment)
end
expose :terminal_path, if: ->(*) { environment.deployment_service_ready? } do |environment|
expose :terminal_path, if: ->(*) { environment.has_terminals? } do |environment|
can?(request.current_user, :admin_environment, environment.project) &&
terminal_project_environment_path(environment.project, environment)
end
......
- if environment.deployment_service_ready? && can?(current_user, :admin_environment, @project)
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
= link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do
= icon('terminal')
......@@ -13,5 +13,5 @@
quick_actions_docs_path: help_page_path('user/project/quick_actions'),
notes_path: notes_url,
last_fetched_at: Time.now.to_i,
issue_data: serialize_issuable(@issue),
noteable_data: serialize_issuable(@issue),
current_user_data: UserSerializer.new.represent(current_user).to_json } }
......@@ -7,7 +7,7 @@ module Geo
end
def schedule_job(object_db_id, object_type)
job_id = GeoFileDownloadWorker.perform_async(object_type, object_db_id)
job_id = FileDownloadWorker.perform_async(object_type, object_db_id)
{ id: object_db_id, type: object_type, job_id: job_id } if job_id
end
......
module Geo
class FileDownloadWorker
include Sidekiq::Worker
sidekiq_options queue: :geo_file_download, retry: 3, dead: false
def perform(object_type, object_id)
Geo::FileDownloadService.new(object_type.to_sym, object_id).execute
end
end
end
......@@ -2,7 +2,7 @@ module Geo
class ProjectSyncWorker
include Sidekiq::Worker
sidekiq_options queue: :geo, retry: 3, dead: false
sidekiq_options queue: :geo_project_sync, retry: 3, dead: false
sidekiq_retry_in { |count| 30 * count }
......
class GeoFileDownloadWorker
include Sidekiq::Worker
include GeoQueue
def perform(object_type, object_id)
Geo::FileDownloadService.new(object_type.to_sym, object_id).execute
end
end
---
title: Fix viewing default push rules on a Geo secondary
merge_request: 3559
author:
type: fixed
---
title: Disable autocomplete for epics
merge_request:
author:
type: fixed
---
title: Fix epic fullscreen editing
merge_request:
author:
type: fixed
---
title: Fix tasklist for epics
merge_request:
author:
type: fixed
---
title: Fix Geo wiki sync error not increasing retry count
merge_request:
author:
type: fixed
---
title: Ensure that rake gitlab:cleanup:repos task does not mess with hashed repositories
merge_request: 15520
author:
type: fixed
---
title: Ensure that rake gitlab:cleanup:dirs task does not mess with hashed repositories
merge_request: 15600
author:
type: fixed
---
title: Fix defaults for MR states and merge statuses
merge_request:
author:
type: fixed
---
title: Fix pulling and pushing using a personal access token with the sudo scope
merge_request:
author:
type: fixed
---
title: Drastically improve project search performance by no longer searching namespace
name
merge_request:
author:
type: performance
---
title: Reuse authors when rendering event Atom feeds
merge_request:
author:
type: performance
---
title: Fix hashed storage for Import/Export uploads
merge_request: 15482
author:
type: fixed
---
title: Fix WIP system note not being created
merge_request:
author:
type: fixed
---
title: Fix link text from group context
merge_request:
author:
type: fixed
---
title: Optimise StuckCiJobsWorker using cheap SQL query outside, and expensive inside
merge_request:
author:
type: performance
......@@ -75,7 +75,8 @@
- [repository_update_remote_mirror, 1]
- [project_update_repository_storage, 1]
- [admin_emails, 1]
- [geo_repository_update, 1]
- [geo_project_sync, 1]
- [geo_file_download, 1]
- [elastic_batch_project_indexer, 1]
- [elastic_indexer, 1]
- [elastic_commit_indexer, 1]
......
......@@ -43,9 +43,13 @@ AuthorizedKeysCommand /opt/gitlab-shell/authorized_keys %u %k
AuthorizedKeysCommandUser git
```
Reload the sshd service:
Reload OpenSSH:
```
```bash
# Debian or Ubuntu installations
sudo service ssh reload
# CentOS installations
sudo service sshd reload
```
......
......@@ -4,50 +4,63 @@
## Legacy Storage
Legacy Storage is the storage behavior prior to version 10.0. For historical reasons, GitLab replicated the same
mapping structure from the projects URLs:
Legacy Storage is the storage behavior prior to version 10.0. For historical
reasons, GitLab replicated the same mapping structure from the projects URLs:
* Project's repository: `#{namespace}/#{project_name}.git`
* Project's wiki: `#{namespace}/#{project_name}.wiki.git`
* Project's repository: `#{namespace}/#{project_name}.git`
* Project's wiki: `#{namespace}/#{project_name}.wiki.git`
This structure made simple to migrate from existing solutions to GitLab and easy for Administrators to find where the
repository is stored.
This structure made it simple to migrate from existing solutions to GitLab and
easy for Administrators to find where the repository is stored.
On the other hand this has some drawbacks:
Storage location will concentrate huge amount of top-level namespaces. The impact can be reduced by the introduction of [multiple storage paths][storage-paths].
Storage location will concentrate huge amount of top-level namespaces. The
impact can be reduced by the introduction of [multiple storage
paths][storage-paths].
Because Backups are a snapshot of the same URL mapping, if you try to recover a very old backup, you need to verify
if any project has taken the place of an old removed project sharing the same URL. This means that `mygroup/myproject`
from your backup may not be the same original project that is today in the same URL.
Because backups are a snapshot of the same URL mapping, if you try to recover a
very old backup, you need to verify whether any project has taken the place of
an old removed or renamed project sharing the same URL. This means that
`mygroup/myproject` from your backup may not be the same original project that
is at that same URL today.
Any change in the URL will need to be reflected on disk (when groups / users or projects are renamed). This can add a lot
of load in big installations, and can be even worst if they are using any type of network based filesystem.
Any change in the URL will need to be reflected on disk (when groups / users or
projects are renamed). This can add a lot of load in big installations,
especially if using any type of network based filesystem.
Last, for GitLab Geo, this storage type means we have to synchronize the disk state, replicate renames in the correct
order or we may end-up with wrong repository or missing data temporarily.
For GitLab Geo in particular: Geo does work with legacy storage, but in some
edge cases due to race conditions it can lead to errors when a project is
renamed multiple times in short succession, or a project is deleted and
recreated under the same name very quickly. We expect these race events to be
rare, and we have not observed a race condition side-effect happening yet.
This pattern also exists in other objects stored in GitLab, like issue Attachments, GitLab Pages artifacts,
Docker Containers for the integrated Registry, etc.
This pattern also exists in other objects stored in GitLab, like issue
Attachments, GitLab Pages artifacts, Docker Containers for the integrated
Registry, etc.
## Hashed Storage
Hashed Storage is the new storage behavior we are rolling out with 10.0. It's not enabled by default yet, but we
encourage everyone to try-it and take the time to fix any script you may have that depends on the old behavior.
> **Warning:** Hashed storage is in **Alpha**. For the latest updates, check the
> associated [issue](https://gitlab.com/gitlab-com/infrastructure/issues/2821)
> and please report any problems you encounter.
Instead of coupling project URL and the folder structure where the repository will be stored on disk, we are coupling
a hash, based on the project's ID.
Hashed Storage is the new storage behavior we are rolling out with 10.0. Instead
of coupling project URL and the folder structure where the repository will be
stored on disk, we are coupling a hash, based on the project's ID. This makes
the folder structure immutable, and therefore eliminates any requirement to
synchronize state from URLs to disk structure. This means that renaming a group,
user, or project will cost only the database transaction, and will take effect
immediately.
This makes the folder structure immutable, and therefore eliminates any requirement to synchronize state from URLs to
disk structure. This means that renaming a group, user or project will cost only the database transaction, and will take
effect immediately.
The hash also helps to spread the repositories more evenly on the disk, so the
top-level directory will contain less folders than the total amount of top-level
namespaces.
The hash also helps to spread the repositories more evenly on the disk, so the top-level directory will contain less
folders than the total amount of top-level namespaces.
Hash format is based on hexadecimal representation of SHA256: `SHA256(project.id)`.
Top-level folder uses first 2 characters, followed by another folder with the next 2 characters. They are both stored in
a special folder `@hashed`, to co-exist with existing Legacy projects:
The hash format is based on the hexadecimal representation of SHA256:
`SHA256(project.id)`. The top-level folder uses the first 2 characters, followed
by another folder with the next 2 characters. They are both stored in a special
`@hashed` folder, to be able to co-exist with existing Legacy Storage projects:
```ruby
# Project's repository:
......@@ -57,15 +70,13 @@ a special folder `@hashed`, to co-exist with existing Legacy projects:
"@hashed/#{hash[0..1]}/#{hash[2..3]}/#{hash}.wiki.git"
```
This new format also makes possible to restore backups with confidence, as when restoring a repository from the backup,
you will never mistakenly restore a repository in the wrong project (considering the backup is made after the migration).
### How to migrate to Hashed Storage
In GitLab, go to **Admin > Settings**, find the **Repository Storage** section and select
"_Create new projects using hashed storage paths_".
In GitLab, go to **Admin > Settings**, find the **Repository Storage** section
and select "_Create new projects using hashed storage paths_".
To migrate your existing projects to the new storage type, check the specific [rake tasks].
To migrate your existing projects to the new storage type, check the specific
[rake tasks].
[ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283
[rake tasks]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage
......@@ -73,11 +84,13 @@ To migrate your existing projects to the new storage type, check the specific [r
### Hashed Storage coverage
We are incrementally moving every storable object in GitLab to the Hashed Storage pattern. You can check the current
coverage status below.
We are incrementally moving every storable object in GitLab to the Hashed
Storage pattern. You can check the current coverage status below (and also see
the [issue](https://gitlab.com/gitlab-com/infrastructure/issues/2821)).
Note that things stored in an S3 compatible endpoint will not have the downsides mentioned earlier, if they are not
prefixed with `#{namespace}/#{project_name}`, which is true for CI Cache and LFS Objects.
Note that things stored in an S3 compatible endpoint will not have the downsides
mentioned earlier, if they are not prefixed with `#{namespace}/#{project_name}`,
which is true for CI Cache and LFS Objects.
| Storable Object | Legacy Storage | Hashed Storage | S3 Compatible | GitLab Version |
| --------------- | -------------- | -------------- | ------------- | -------------- |
......
......@@ -87,16 +87,12 @@ Meanwhile, the primary node will start to notify changes to the secondary, which
will act on those notifications immediately. Make sure the secondary instance is
running and accessible.
### Step 2. Enabling hashed storage (from GitLab 10.0)
### Step 2. Enabling hashed storage (optional, from GitLab 10.0)
>**Warning**
Hashed storage is in **Beta**. It is considered experimental and not
production-ready. For the latest updates, check
[issue](https://gitlab.com/gitlab-com/infrastructure/issues/2821).
Hashed Storage is not required to run GitLab Geo, but in some edge cases race
conditions can lead to errors and Geo to break. Known issues are renaming a
project multiple times in short succession, deleting a project and recreating
with the same name very quickly.
Hashed storage is in **Alpha**. It is considered experimental and not
production-ready. See [Hashed
Storage](../administration/repository_storage_types.md) for more detail.
Using hashed storage significantly improves Geo replication - project and group
renames no longer require synchronization between nodes.
......
......@@ -91,21 +91,12 @@ primary in a process known as backfill. Meanwhile, the primary node will start
to notify changes to the secondary, which will act on those notifications
immediately. Make sure the secondary instance is running and accessible.
### Step 2. Enabling hashed storage (from GitLab 10.0)
### Step 2. Enabling hashed storage (optional, GitLab 10.0)
>**Note:**
Hashed storage is in **Beta**. It is considered experimental and not
production-ready. For the latest updates, check
[issue](https://gitlab.com/gitlab-com/infrastructure/issues/2821).
Hashed Storage is not required to run GitLab Geo, but in some edge cases race
conditions can lead to errors and Geo to break. Known issues are renaming a
project multiple times in short succession, deleting a project and recreating
with the same name very quickly.
>**Note:**
Instances already using hashed storage are not recommended to disable hashed
storage, since bugs affecting hashed storage would continue to affect these
projects.
>**Warning**
Hashed storage is in **Alpha**. It is considered experimental and not
production-ready. See [Hashed
Storage](../administration/repository_storage_types.md) for more detail.
Using hashed storage significantly improves Geo replication - project and group
renames no longer require synchronization between nodes.
......
......@@ -26,7 +26,7 @@ connect to secondary database servers (which are read-only too).
In many databases documentation you will see "primary" being referenced as "master"
and "secondary" as either "slave" or "standby" server (read-only).
Since GitLab 9.4: We recommend using [PostgreSQL replication
We recommend using [PostgreSQL replication
slots](https://medium.com/@tk512/replication-slots-in-postgresql-b4b03d277c75)
to ensure the primary retains all the data necessary for the secondaries to
recover. See below for more details.
......@@ -80,6 +80,7 @@ will not be able to perform all necessary configuration steps. Refer to
else.
1. Set up TLS support for the PostgreSQL primary server
> **Warning**: Only skip this step if you **know** that PostgreSQL traffic
> between the primary and secondary will be secured through some other
> means, e.g., a known-safe physical network path or a site-to-site VPN that
......@@ -146,104 +147,115 @@ will not be able to perform all necessary configuration steps. Refer to
postgresql['ssl'] = 'on'
```
1. Configure PostgreSQL to listen on an external network interface
1. Configure PostgreSQL to listen on network interfaces
Edit `/etc/gitlab/gitlab.rb` and add the following. Note that GitLab 9.1 added
the `geo_primary_role` configuration variable:
For security reasons, PostgreSQL does not listen on any network interfaces
by default. However, GitLab Geo requires the secondary to be able to
connect to the primary's database. For this reason, we need the address of
each node.
```ruby
geo_primary_role['enable'] = true
postgresql['listen_address'] = '1.2.3.4'
postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','1.2.3.4/32']
postgresql['md5_auth_cidr_addresses'] = ['5.6.7.8/32']
# New for 9.4: Set this to be the number of Geo secondary nodes you have
postgresql['max_replication_slots'] = 1
# postgresql['max_wal_senders'] = 10
# postgresql['wal_keep_segments'] = 10
```
If you are using a cloud provider, you can lookup the addresses for each
Geo node through their management console. A table of terminology is
provided below because terminology varies between vendors.
For external PostgreSQL instances, [see additional instructions][external postgresql].
| GitLab Terminology | Amazon Web Services | Google Cloud Platform |
|-----|-----|-----|-----|
| Interface address | Private address | Internal address |
| Public address | Public address | External address |
To lookup the address of a Geo node, on the Geo node execute:
Where `1.2.3.4` is the IP address of the primary server, and `5.6.7.8`
is the IP address of the secondary one.
```bash
# Interface address
ip route get 255.255.255.255 | awk '{print $NF; exit}'
For security reasons, PostgreSQL by default only listens on the local
interface (e.g. 127.0.0.1). However, GitLab Geo needs to communicate
between the primary and secondary nodes over a common network, such as a
corporate LAN or the public Internet. For this reason, we need to
configure PostgreSQL to listen on more interfaces.
# Public address
curl ipinfo.io/ip
```
The `listen_address` option opens PostgreSQL up to external connections
with the interface corresponding to the given IP. See [the PostgreSQL
documentation](https://www.postgresql.org/docs/9.6/static/runtime-config-connection.html)
for more details.
In most cases, the following addresses will be used to configure GitLab
Geo:
Note that if you are running GitLab Geo with a cloud provider (e.g. Amazon
Web Services), the internal interface IP (as provided by `ifconfig`) may
be different from the public IP address. For example, suppose you have a
nodes with the following configuration:
| Configuration | Address |
|-----|-----|
| `postgresql['listen_address']` | Primary's interface address |
| `postgresql['trust_auth_cidr_addresses']` | Primary's interface address |
| `postgresql['md5_auth_cidr_addresses']` | Secondary's public addresses |
|Node Type|Internal IP|External IP|
|---------|-----------|-----------|
|Primary|10.1.5.3|54.193.124.100|
|Secondary|10.1.10.5|54.193.100.155|
The `listen_address` option opens PostgreSQL up to network connections
with the interface corresponding to the given address. See [the PostgreSQL
documentation](https://www.postgresql.org/docs/9.6/static/runtime-config-connection.html)
for more details.
If you are running two nodes in different cloud availability zones, you
may need to double check that the nodes can communicate over the internal
IP addresses. For example, servers on Amazon Web Services in the same
[Virtual Private Cloud (VPC)](https://aws.amazon.com/vpc/) can do
this. Google Compute Engine also offers an [internal network]
(https://cloud.google.com/compute/docs/networking) that supports
cross-availability zone networking.
Depending on your network configuration, the suggested addresses may not
be correct. If your primary and secondary connect over a local
area network, or a virtual network connecting availability zones like
Amazon's [VPC](https://aws.amazon.com/vpc/) of Google's [VPC](https://cloud.google.com/vpc/)
you should use the secondary's interface address for `postgresql['md5_auth_cidr_addresses']`.
For the above example, the following configuration uses the internal IPs
to replicate the database from the primary to the secondary:
Edit `/etc/gitlab/gitlab.rb` and add the following, replacing the IP
addresses with addresses appropriate to your network configuration:
```ruby
# Example configuration using internal IPs for a cloud configuration
geo_primary_role['enable'] = true
postgresql['listen_address'] = '10.1.5.3'
postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','10.1.5.3/32']
postgresql['md5_auth_cidr_addresses'] = ['10.1.10.5/32']
postgresql['max_replication_slots'] = 1 # Number of Geo secondary nodes
# Primary address
# - replace '1.2.3.4' with the primary interface address
postgresql['listen_address'] = '1.2.3.4'
postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','1.2.3.4/32']
# Secondary addresses
# - replace '5.6.7.8' with the secondary public address
postgresql['md5_auth_cidr_addresses'] = ['5.6.7.8/32']
# Replication settings
# - set this to be the number of Geo secondary nodes you have
postgresql['max_replication_slots'] = 1
# postgresql['max_wal_senders'] = 10
# postgresql['wal_keep_segments'] = 10
# Disable automatic database migrations for now
# (until PostgreSQL is restarted and listening on the interface address)
gitlab_rails['auto_migrate'] = false
```
If you prefer that your nodes communicate over the public Internet, you
may choose the IP addresses from the "External IP" column above.
For external PostgreSQL instances, [see additional instructions][external postgresql].
1. Optional: If you want to add another secondary, the relevant setting would look like:
```ruby
postgresql['md5_auth_cidr_addresses'] = ['5.6.7.8/32','11.22.33.44/32']
postgresql['md5_auth_cidr_addresses'] = ['5.6.7.8/32','9.10.11.12/32']
```
You may also want to edit the `wal_keep_segments` and `max_wal_senders` to
match your database replication requirements. Consult the [PostgreSQL - Replication documentation](https://www.postgresql.org/docs/9.6/static/runtime-config-replication.html)
match your database replication requirements. Consult the [PostgreSQL -
Replication documentation](https://www.postgresql.org/docs/9.6/static/runtime-config-replication.html)
for more information.
1. Save the file and [reconfigure GitLab][] for the database listen changes to
take effect.
**This step will fail.** This is caused by
[Omnibus#2797](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2797).
1. Save the file and [reconfigure GitLab][] for the database listen changes and
the replication slot changes to be applied.
Restart PostgreSQL:
Restart PostgreSQL for its changes to take effect:
```bash
gitlab-ctl restart postgresql
```
[Reconfigure GitLab][reconfigure GitLab] again. It should complete cleanly.
1. Reenable migrations
Edit `/etc/gitlab/gitlab.rb` and **delete** the following lines:
```ruby
# Disable automatic database migrations for now
# (until PostgreSQL is restarted and listening on the interface address)
gitlab_rails['auto_migrate'] = false
```
1. New for 9.4: Restart your primary PostgreSQL server to ensure the
replication slot changes take effect (`sudo gitlab-ctl restart postgresql`
for Omnibus-provided PostgreSQL).
Save the file and [reconfigure GitLab][].
1. Now that the PostgreSQL server is set up to accept remote connections, run
`netstat -plnt` to make sure that PostgreSQL is listening on port `5432` to
the server's public IP.
the server's interface address.
1. Verify that clock synchronization is enabled.
......
......@@ -53,18 +53,29 @@ secondary if ever promoted to a primary:
sudo -u git -H rm ~git/.ssh/id_rsa ~git/.ssh/id_rsa.pub
```
### Hashed Storage
>**Warning**
Hashed storage is in **Alpha**. It is considered experimental and not
production-ready. See [Hashed
Storage](../administration/repository_storage_types.md) for more detail.
If you previously enabled Hashed Storage and migrated all your existing
projects to Hashed Storage, disabling hashed storage will not migrate projects
to their previous project based storage path. As such, once enabled and
migrated we recommend leaving Hashed Storage enabled.
## Upgrading to GitLab 10.1
>**Warning**
Hashed storage is in **Alpha**. It is considered experimental and not
production-ready. See [Hashed
Storage](../administration/repository_storage_types.md) for more detail.
[Hashed storage](../administration/repository_storage_types.md) was introduced
in GitLab 10.0, and a [migration path](../administration/raketasks/storage.md)
for existing repositories was added in GitLab 10.1.
After upgrading to GitLab 10.1, we recommend that you
[enable hashed storage for all new projects](#step-5-enabling-hashed-storage-from-gitlab-100),
then [migrate existing projects to hashed storage](../administration/raketasks/storage.md).
This will significantly reduce the amount of synchronization required between
nodes in the event of project or group renames.
## Upgrading to GitLab 10.0
Since GitLab 10.0, we require all **Geo** systems to [use SSH key lookups via
......
......@@ -22,7 +22,7 @@ Among numerous use cases for exporting issues for CSV, we can name a few:
- Make a snapshot of issues for offline analysis or to communicate with other teams who may not be in GitLab
- Create diagrams, graphs, and charts from the CSV data
- Present the data in any other format for auditing or sharing reasons
- Import the issues elsewhere
- Import the issues elsewhere to a system outside of GitLab
- Long-term issues' data analysis with multiple snapshots created along the time
- Use the long-term data to gather relevant feedback given in the issues, and improve your product based on real metrics
......
......@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-11-02 14:42+0100\n"
"PO-Revision-Date: 2017-11-03 12:30-0400\n"
"PO-Revision-Date: 2017-11-28 11:32-0500\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: German\n"
"Language: de_DE\n"
......@@ -2070,7 +2070,7 @@ msgid "Time until first merge request"
msgstr "Zeit bis zum ersten Merge Request"
msgid "Timeago|%s days ago"
msgstr ""
msgstr "vor %s Tagen"
msgid "Timeago|%s days remaining"
msgstr "%s Tage verbleibend"
......
......@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-11-02 14:42+0100\n"
"PO-Revision-Date: 2017-11-03 12:30-0400\n"
"PO-Revision-Date: 2017-11-21 16:43-0500\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: French\n"
"Language: fr_FR\n"
......@@ -36,8 +36,8 @@ msgstr "%{commit_author_link} a validé %{commit_timeago}"
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "%{count} participant•e"
msgstr[1] "%{count} participant•e•s"
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr "%{number_commits_behind} validations de retard sur %{default_branch}, %{number_commits_ahead} validations d'avance"
......@@ -60,10 +60,10 @@ msgid "(checkout the %{link} for information on how to install it)."
msgstr "(Lisez %{link} pour savoir comment l'installer)."
msgid "+ %{moreCount} more"
msgstr ""
msgstr "+ %{moreCount} de plus"
msgid "- show less"
msgstr ""
msgstr "- en montrer moins"
msgid "1 pipeline"
msgid_plural "%d pipelines"
......@@ -125,7 +125,7 @@ msgid "AdminHealthPageLink|health page"
msgstr "État des services"
msgid "Advanced settings"
msgstr ""
msgstr "Paramètres avancés"
msgid "All"
msgstr "Tous"
......@@ -625,8 +625,8 @@ msgstr[1] "Validations"
msgid "Commit %d file"
msgid_plural "Commit %d files"
msgstr[0] ""
msgstr[1] ""
msgstr[0] "Valider %d fichier"
msgstr[1] "Valider %d fichiers"
msgid "Commit Message"
msgstr "Message de validation"
......@@ -692,10 +692,10 @@ msgid "ContainerRegistry|Size"
msgstr "Taille"
msgid "ContainerRegistry|Tag"
msgstr "Étiquette"
msgstr "Tag"
msgid "ContainerRegistry|Tag ID"
msgstr "ID d‘étiquette"
msgstr "ID du tag"
msgid "ContainerRegistry|Use different image names"
msgstr "Utilisez des noms d’images différents"
......@@ -704,7 +704,7 @@ msgid "ContainerRegistry|With the Docker Container Registry integrated into GitL
msgstr "Avec le registre de conteneur Docker intégré à GitLab, chaque projet peut avoir son propre espace pour stocker ses images Docker."
msgid "Contribution guide"
msgstr "Guilde de contribution"
msgstr "Guide de contribution"
msgid "Contributors"
msgstr "Contributeurs"
......@@ -737,7 +737,7 @@ msgid "Create empty bare repository"
msgstr "Créer un dépôt vide"
msgid "Create file"
msgstr ""
msgstr "Créer un fichier"
msgid "Create merge request"
msgstr "Créer une demande de fusion"
......@@ -746,10 +746,10 @@ msgid "Create new branch"
msgstr "Créer une nouvelle branche"
msgid "Create new directory"
msgstr ""
msgstr "Créer un nouveau dossier"
msgid "Create new file"
msgstr ""
msgstr "Créer un nouveau fichier"
msgid "Create new..."
msgstr "Créer nouveau..."
......@@ -922,7 +922,7 @@ msgid "Failed to remove the pipeline schedule"
msgstr "Échec de la suppression du pipeline programmé"
msgid "File name"
msgstr ""
msgstr "Nom du fichier"
msgid "Files"
msgstr "Fichiers"
......@@ -1364,10 +1364,10 @@ msgid "Notifications"
msgstr "Notifications"
msgid "Number of access attempts"
msgstr ""
msgstr "Nombre de tentatives d'accès"
msgid "Number of failures before backing off"
msgstr ""
msgstr "Nombre d'échecs avant annulation"
msgid "OfSearchInADropdown|Filter"
msgstr "Filtre"
......@@ -1625,7 +1625,7 @@ msgid "ProjectSettings|This setting will be applied to all projects unless overr
msgstr "Ce paramètre s’appliquera à tous les projets à moins qu’un administrateur ne le modifie."
msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails."
msgstr ""
msgstr "Les utilisateurs peuvent uniquement pousser sur ce dépôt des validations qui ont été validées avec une de leurs adresses courriels vérifiées."
msgid "Projects"
msgstr "Projets"
......@@ -1664,7 +1664,7 @@ msgid "Push events"
msgstr "Évènements de poussée"
msgid "PushRule|Committer restriction"
msgstr ""
msgstr "Restriction du validateur"
msgid "Read more"
msgstr "Lire plus"
......@@ -1682,7 +1682,7 @@ msgid "Registry"
msgstr "Registre"
msgid "Related Commits"
msgstr "Validations liés"
msgstr "Validations liées"
msgid "Related Deployed Jobs"
msgstr "Tâches de déploiement liées"
......@@ -1730,7 +1730,7 @@ msgid "SSH Keys"
msgstr "Clés SSH"
msgid "Save"
msgstr ""
msgstr "Enregistrer"
msgid "Save changes"
msgstr "Enregistrer les modifications"
......@@ -1939,10 +1939,10 @@ msgid "Subgroups"
msgstr "Sous-groupes"
msgid "Subscribe"
msgstr ""
msgstr "S’abonner"
msgid "Switch branch/tag"
msgstr "Changer de branche / d'étiquette"
msgstr "Changer de branche / tag"
msgid "System Hooks"
msgstr "Crochets système"
......@@ -1968,7 +1968,7 @@ msgid "The Advanced Global Search in GitLab is a powerful search service that sa
msgstr "La Recherche Globale Avancée de GitLab est un outils qui vous fait gagner du temps. Au lieu de créer du code similaire et perdre du temps, vous pouvez maintenant chercher dans le code d'autres équipes pour vous aider sur votre projet."
msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold"
msgstr ""
msgstr "Le seuil d’interruption du disjoncteur devrait être inférieur au seuil de nombre de défaillance"
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr "L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion."
......@@ -1983,10 +1983,10 @@ msgid "The issue stage shows the time it takes from creating an issue to assigni
msgstr "L'étape des tickets montre le temps nécessaire entre la création d'un ticket et son assignation à un jalon, ou son ajout à une liste d'un tableau de tickets. Commencez par créer des tickets pour voir des données pour cette étape."
msgid "The number of attempts GitLab will make to access a storage."
msgstr ""
msgstr "Le nombre de tentatives que GitLab va effectuer pour accéder au stockage."
msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host"
msgstr ""
msgstr "Le nombre d'échecs avant que GitLab ne commence à désactiver l'accès à la partition de stockage sur l'hôte"
msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
msgstr "Nombre d’échecs avant que GitLab n’empêche tout accès au stockage. Ce nombre d’échecs peut être réinitialisé dans l’interface d’administration : %{link_to_health_page} ou en suivant le %{api_documentation_link}."
......@@ -2224,7 +2224,7 @@ msgid "Unstar"
msgstr "Supprimer des favoris"
msgid "Unsubscribe"
msgstr ""
msgstr "Se désabonner"
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr "Mettez à jour votre abonnement pour activer la Recherche Globale Avancée."
......
......@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-11-02 14:42+0100\n"
"PO-Revision-Date: 2017-11-03 12:31-0400\n"
"PO-Revision-Date: 2017-11-20 03:59-0500\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Italian\n"
"Language: it_IT\n"
......@@ -2085,7 +2085,7 @@ msgid "Timeago|%s minutes remaining"
msgstr "%s minuti rimanenti"
msgid "Timeago|%s months ago"
msgstr "%s minuti fa"
msgstr "%s mesi fa"
msgid "Timeago|%s months remaining"
msgstr "%s mesi rimanenti"
......
This diff is collapsed.
......@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-11-02 14:42+0100\n"
"PO-Revision-Date: 2017-11-04 13:27-0400\n"
"PO-Revision-Date: 2017-11-18 12:51-0500\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Portuguese, Brazilian\n"
"Language: pt_BR\n"
......@@ -361,10 +361,10 @@ msgid "Browse Directory"
msgstr "Navegar no Diretório"
msgid "Browse File"
msgstr "Pesquisar Arquivo"
msgstr "Acessar arquivo"
msgid "Browse Files"
msgstr "Pesquisar Arquivos"
msgstr "Acessar arquivos"
msgid "Browse files"
msgstr "Navegar pelos arquivos"
......@@ -1367,7 +1367,7 @@ msgid "Number of access attempts"
msgstr "Número de tentativas de acesso"
msgid "Number of failures before backing off"
msgstr ""
msgstr "Número de falhas antes de reverter"
msgid "OfSearchInADropdown|Filter"
msgstr "Filtrar"
......@@ -1968,7 +1968,7 @@ msgid "The Advanced Global Search in GitLab is a powerful search service that sa
msgstr "A pesquisa global avançado no GitLab é um serviço poderoso de pesquisa que poupa seu tempo. Ao invés de criar código duplicado e perder seu tempo, você pode agora pesquisar por código de outros times que podem ajudar no seu próprio projeto."
msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold"
msgstr ""
msgstr "O limite do recuso do circuitbreaker deve ser inferior ao limite de contagem de falhas"
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr "A etapa de codificação mostra o tempo desde a entrega do primeiro commit até a criação do merge request. Os dados serão automaticamente adicionados aqui desde o momento de criação do merge request."
......
......@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-11-02 14:42+0100\n"
"PO-Revision-Date: 2017-11-05 14:39-0500\n"
"PO-Revision-Date: 2017-11-17 07:55-0500\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
......@@ -188,7 +188,7 @@ msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly
msgstr "Приложения для автоматического ревью и автоматического развёртывания требуют указания %{kubernetes} для корректной работы."
msgid "AutoDevOps|Auto DevOps (Beta)"
msgstr ""
msgstr "Auto DevOps (бета)"
msgid "AutoDevOps|Auto DevOps documentation"
msgstr ""
......
This diff is collapsed.
......@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-11-02 14:42+0100\n"
"PO-Revision-Date: 2017-11-06 01:25-0500\n"
"PO-Revision-Date: 2017-11-23 02:44-0500\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
......@@ -656,10 +656,10 @@ msgid "ContainerRegistry|Created"
msgstr "已创建"
msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
msgstr "首先使用您的 GitLab 用户名和密码登录 GitLab 的容器注册表。如果您有%{link_2fa},您需要使用%{link_token}:"
msgstr "首先使用您的 GitLab 用户名和密码登录 GitLab 的容器注册表。如果您已经%{link_2fa},则需要使用%{link_token}:"
msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
msgstr "GitLab 最多支持3个级别的镜像名称。以下镜像示例对您的项目有效:"
msgstr "GitLab 最多支持3个级别的镜像命名。以下镜像名称示例对当前项目有效:"
msgid "ContainerRegistry|How to use the Container Registry"
msgstr "如何使用容器注册表"
......@@ -671,7 +671,7 @@ msgid "ContainerRegistry|No tags in Container Registry for this container image.
msgstr "容器注册表中没有此容器镜像的标签。"
msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands"
msgstr "登录后您可以使用通用的%{build}和%{push}命令自由创建和上传容器映像"
msgstr "登录后您可以使用通用的%{build}和%{push}命令创建和上传容器镜像"
msgid "ContainerRegistry|Remove repository"
msgstr "删除存储库"
......@@ -692,7 +692,7 @@ msgid "ContainerRegistry|Use different image names"
msgstr "使用不同的镜像名称"
msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
msgstr "将 Docker 容器注册表集成到 GitLab 中,每个项目都可以有自己的空间来存储 Docker 的图像。"
msgstr "将 Docker 容器注册表集成到 GitLab 中,每个项目都可以有各自的空间来存储 Docker 的镜像。"
msgid "Contribution guide"
msgstr "贡献指南"
......@@ -1155,7 +1155,7 @@ msgid "Last update"
msgstr "最后更新"
msgid "Last updated"
msgstr "最更新"
msgstr "最更新"
msgid "LastPushEvent|You pushed to"
msgstr "您推送了"
......@@ -1274,7 +1274,7 @@ msgid "New tag"
msgstr "新建标签"
msgid "No container images stored for this project. Add one by following the instructions above."
msgstr "没有为此项目存储容器镜像。请按照上述说明添加一个。"
msgstr "此项目当前未存储容器镜像。如需使用,请参照上述说明新建容器镜像。"
msgid "No repository"
msgstr "没有存储库"
......@@ -1361,7 +1361,7 @@ msgid "Only project members can comment."
msgstr "只有项目成员可以发表评论。"
msgid "OpenedNDaysAgo|Opened"
msgstr "开始于"
msgstr "创建于"
msgid "Opens in a new window"
msgstr "打开一个新窗口"
......@@ -1775,10 +1775,10 @@ msgid "Settings"
msgstr "设置"
msgid "Show parent pages"
msgstr "查看页面"
msgstr "查看上级页面"
msgid "Show parent subgroups"
msgstr "查看群组中的子群组"
msgstr "查看上级子群组"
msgid "Showing %d event"
msgid_plural "Showing %d events"
......@@ -1830,13 +1830,13 @@ msgid "SortOptions|Largest repository"
msgstr "最大存储库"
msgid "SortOptions|Last created"
msgstr "最创建"
msgstr "最创建"
msgid "SortOptions|Last joined"
msgstr "最新加入"
msgid "SortOptions|Last updated"
msgstr "最更新"
msgstr "最更新"
msgid "SortOptions|Least popular"
msgstr "最不受欢迎"
......@@ -1869,7 +1869,7 @@ msgid "SortOptions|Name, descending"
msgstr "名称,降序排列"
msgid "SortOptions|Oldest created"
msgstr "最早创建"
msgstr "最早创建"
msgid "SortOptions|Oldest joined"
msgstr "最早的加入"
......@@ -1878,7 +1878,7 @@ msgid "SortOptions|Oldest sign in"
msgstr "最早的登录"
msgid "SortOptions|Oldest updated"
msgstr "最早的提交"
msgstr "最早更新"
msgid "SortOptions|Popularity"
msgstr "人气"
......@@ -1945,7 +1945,7 @@ msgid "Team"
msgstr "团队"
msgid "Thanks! Don't show me this again"
msgstr "谢谢 ! 请不要再显示"
msgstr "不再显示该提示"
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr "GitLab 中的高级全局搜索功能是非常强大的搜索服务。您可以搜索其他团队的代码以帮助您完善自己项目中的代码。从而避免创建重复的代码或浪费时间。"
......@@ -2465,7 +2465,7 @@ msgstr "通知邮件"
msgid "parent"
msgid_plural "parents"
msgstr[0] "级"
msgstr[0] "级"
msgid "password"
msgstr "密码"
......
......@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: gitlab-ee\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-11-02 14:42+0100\n"
"PO-Revision-Date: 2017-11-03 12:30-0400\n"
"PO-Revision-Date: 2017-11-15 02:54-0500\n"
"Last-Translator: gitlab <mbartlett+crowdin@gitlab.com>\n"
"Language-Team: Chinese Traditional, Hong Kong\n"
"Language: zh_HK\n"
......@@ -609,7 +609,7 @@ msgid "ClusterIntegration|properly configured"
msgstr ""
msgid "Comments"
msgstr "評論 (Comment)"
msgstr "評論"
msgid "Commit"
msgid_plural "Commits"
......@@ -876,7 +876,7 @@ msgid "EventFilterBy|Filter by all"
msgstr "全部"
msgid "EventFilterBy|Filter by comments"
msgstr "按評論 (comment) 過濾"
msgstr "按評論過濾"
msgid "EventFilterBy|Filter by issue events"
msgstr "按議題事件 (issue event) 過濾"
......
This diff is collapsed.
......@@ -130,11 +130,35 @@ describe Projects::BranchesController do
expect(response).to have_gitlab_http_status(302)
end
end
<<<<<<< HEAD
context 'when user configured kubernetes from Integration > Kubernetes' do
before do
project.services << build(:kubernetes_service)
end
=======
context 'when user configured kubernetes from Integration > Kubernetes' do
before do
project.services << build(:kubernetes_service)
end
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
context 'when user configured kubernetes from CI/CD > Clusters' do
before do
create(:cluster, :provided_by_gcp, projects: [project])
end
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
it 'redirects to autodeploy setup page' do
result = { status: :success, branch: double(name: branch) }
create(:cluster, :provided_by_gcp, projects: [project])
>>>>>>> origin/master
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
......
......@@ -34,7 +34,7 @@ describe Projects::EnvironmentsController do
context 'when requesting JSON response for folders' do
before do
allow_any_instance_of(Environment).to receive(:deployment_service_ready?).and_return(true)
allow_any_instance_of(Environment).to receive(:has_terminals?).and_return(true)
allow_any_instance_of(Environment).to receive(:rollout_status).and_return(kube_deployment_rollout_status)
create(:environment, project: project,
......
......@@ -43,6 +43,17 @@ describe 'Epic Issues', :js do
end
context 'when user is a group member' do
let(:issue_to_add) { create(:issue, project: private_project) }
let(:issue_invalid) { create(:issue) }
def add_issues(references)
find('.related-issues-block h3.panel-title button').click
find('.js-add-issuable-form-input').set(references)
find('.js-add-issuable-form-add-button').click
wait_for_requests
end
before do
group.add_developer(user)
visit_epic
......@@ -64,16 +75,20 @@ describe 'Epic Issues', :js do
end
end
it 'user cannot add new issues to the epic from another group' do
add_issues("#{issue_invalid.to_reference(full: true)}")
expect(page).to have_selector('.content-wrapper .alert-wrapper .flash-text')
expect(find('.flash-alert')).to have_text('No Issue found for given params')
end
it 'user can add new issues to the epic' do
issue_to_add = create(:issue, project: private_project)
issue_invalid = create(:issue)
references = "#{issue_to_add.to_reference(full: true)} #{issue_invalid.to_reference(full: true)}"
find('.related-issues-block h3.panel-title button').click
find('.js-add-issuable-form-input').set references
find('.js-add-issuable-form-add-button').click
add_issues(references)
wait_for_requests
expect(page).not_to have_selector('.content-wrapper .alert-wrapper .flash-text')
expect(page).not_to have_content('No Issue found for given params')
within('.related-issues-block ul.issuable-list') do
expect(page).to have_selector('li', count: 3)
......
......@@ -4,8 +4,8 @@ describe KubernetesService, models: true, use_clean_rails_memory_store_caching:
include KubernetesHelpers
include ReactiveCachingHelpers
let(:project) { build_stubbed(:kubernetes_project) }
let(:service) { project.kubernetes_service }
shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
let(:service) { project.deployment_platform }
describe '#rollout_status' do
let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
......@@ -51,4 +51,18 @@ describe KubernetesService, models: true, use_clean_rails_memory_store_caching:
end
end
end
end
context 'when user configured kubernetes from Integration > Kubernetes' do
let(:project) { create(:kubernetes_project) }
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
context 'when user configured kubernetes from CI/CD > Clusters' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
end
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import Autosize from 'autosize';
import store from '~/notes/stores';
import issueCommentForm from '~/notes/components/issue_comment_form.vue';
import { loggedOutIssueData, notesDataMock, userDataMock, issueDataMock } from '../mock_data';
import { loggedOutIssueData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data';
import { keyboardDownEvent } from '../../issue_show/helpers';
describe('issue_comment_form component', () => {
......@@ -23,7 +23,7 @@ describe('issue_comment_form component', () => {
describe('user is logged in', () => {
beforeEach(() => {
store.dispatch('setUserData', userDataMock);
store.dispatch('setIssueData', issueDataMock);
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
vm = mountComponent();
......@@ -178,7 +178,7 @@ describe('issue_comment_form component', () => {
describe('issue is confidential', () => {
it('shows information warning', (done) => {
store.dispatch('setIssueData', Object.assign(issueDataMock, { confidential: true }));
store.dispatch('setNoteableData', Object.assign(noteableDataMock, { confidential: true }));
Vue.nextTick(() => {
expect(vm.$el.querySelector('.confidential-issue-warning')).toBeDefined();
done();
......@@ -190,7 +190,7 @@ describe('issue_comment_form component', () => {
describe('user is not logged in', () => {
beforeEach(() => {
store.dispatch('setUserData', null);
store.dispatch('setIssueData', loggedOutIssueData);
store.dispatch('setNoteableData', loggedOutIssueData);
store.dispatch('setNotesData', notesDataMock);
vm = mountComponent();
......
import Vue from 'vue';
import store from '~/notes/stores';
import issueDiscussion from '~/notes/components/issue_discussion.vue';
import { issueDataMock, discussionMock, notesDataMock } from '../mock_data';
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
describe('issue_discussion component', () => {
let vm;
......@@ -9,7 +9,7 @@ describe('issue_discussion component', () => {
beforeEach(() => {
const Component = Vue.extend(issueDiscussion);
store.dispatch('setIssueData', issueDataMock);
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
vm = new Component({
......
......@@ -24,7 +24,7 @@ describe('issue_note_app', () => {
mountComponent = (data) => {
const props = data || {
issueData: mockData.issueDataMock,
noteableData: mockData.noteableDataMock,
notesData: mockData.notesDataMock,
userData: mockData.userDataMock,
};
......@@ -60,7 +60,7 @@ describe('issue_note_app', () => {
});
it('should set issue data', () => {
expect(vm.$store.state.issueData).toEqual(mockData.issueDataMock);
expect(vm.$store.state.noteableData).toEqual(mockData.noteableDataMock);
});
it('should set user data', () => {
......
import Vue from 'vue';
import store from '~/notes/stores';
import awardsNote from '~/notes/components/issue_note_awards_list.vue';
import { issueDataMock, notesDataMock } from '../mock_data';
import { noteableDataMock, notesDataMock } from '../mock_data';
describe('issue_note_awards_list component', () => {
let vm;
......@@ -10,7 +10,7 @@ describe('issue_note_awards_list component', () => {
beforeEach(() => {
const Component = Vue.extend(awardsNote);
store.dispatch('setIssueData', issueDataMock);
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
awardsMock = [
{
......
......@@ -2,7 +2,7 @@
import Vue from 'vue';
import store from '~/notes/stores';
import noteBody from '~/notes/components/issue_note_body.vue';
import { issueDataMock, notesDataMock, note } from '../mock_data';
import { noteableDataMock, notesDataMock, note } from '../mock_data';
describe('issue_note_body component', () => {
let vm;
......@@ -10,7 +10,7 @@ describe('issue_note_body component', () => {
beforeEach(() => {
const Component = Vue.extend(noteBody);
store.dispatch('setIssueData', issueDataMock);
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
vm = new Component({
......
import Vue from 'vue';
import store from '~/notes/stores';
import issueNoteForm from '~/notes/components/issue_note_form.vue';
import { issueDataMock, notesDataMock } from '../mock_data';
import { noteableDataMock, notesDataMock } from '../mock_data';
import { keyboardDownEvent } from '../../issue_show/helpers';
describe('issue_note_form component', () => {
......@@ -11,7 +11,7 @@ describe('issue_note_form component', () => {
beforeEach(() => {
const Component = Vue.extend(issueNoteForm);
store.dispatch('setIssueData', issueDataMock);
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
props = {
......
......@@ -2,7 +2,7 @@
import Vue from 'vue';
import store from '~/notes/stores';
import issueNote from '~/notes/components/issue_note.vue';
import { issueDataMock, notesDataMock, note } from '../mock_data';
import { noteableDataMock, notesDataMock, note } from '../mock_data';
describe('issue_note', () => {
let vm;
......@@ -10,7 +10,7 @@ describe('issue_note', () => {
beforeEach(() => {
const Component = Vue.extend(issueNote);
store.dispatch('setIssueData', issueDataMock);
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
vm = new Component({
......
......@@ -18,7 +18,7 @@ export const userDataMock = {
username: 'root',
};
export const issueDataMock = {
export const noteableDataMock = {
assignees: [],
author_id: 1,
branch_name: null,
......
import * as actions from '~/notes/stores/actions';
import testAction from '../../helpers/vuex_action_helper';
import { discussionMock, notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data';
import { discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data';
describe('Actions Notes Store', () => {
describe('setNotesData', () => {
......@@ -11,10 +11,10 @@ describe('Actions Notes Store', () => {
});
});
describe('setIssueData', () => {
describe('setNoteableData', () => {
it('should set received issue data', (done) => {
testAction(actions.setIssueData, null, { issueData: {} }, [
{ type: 'SET_ISSUE_DATA', payload: issueDataMock },
testAction(actions.setNoteableData, null, { noteableData: {} }, [
{ type: 'SET_NOTEABLE_DATA', payload: noteableDataMock },
], done);
});
});
......
import * as getters from '~/notes/stores/getters';
import { notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data';
import { notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data';
describe('Getters Notes Store', () => {
let state;
......@@ -11,7 +11,7 @@ describe('Getters Notes Store', () => {
notesData: notesDataMock,
userData: userDataMock,
issueData: issueDataMock,
noteableData: noteableDataMock,
};
});
describe('notes', () => {
......@@ -32,9 +32,9 @@ describe('Getters Notes Store', () => {
});
});
describe('getIssueData', () => {
it('should return all data in `issueData`', () => {
expect(getters.getIssueData(state)).toEqual(issueDataMock);
describe('getNoteableData', () => {
it('should return all data in `noteableData`', () => {
expect(getters.getNoteableData(state)).toEqual(noteableDataMock);
});
});
......
import mutations from '~/notes/stores/mutations';
import { note, discussionMock, notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data';
import { note, discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data';
describe('Mutation Notes Store', () => {
describe('ADD_NEW_NOTE', () => {
......@@ -74,14 +74,14 @@ describe('Mutation Notes Store', () => {
});
});
describe('SET_ISSUE_DATA', () => {
describe('SET_NOTEABLE_DATA', () => {
it('should set the issue data', () => {
const state = {
issueData: {},
noteableData: {},
};
mutations.SET_ISSUE_DATA(state, issueDataMock);
expect(state.issueData).toEqual(issueDataMock);
mutations.SET_NOTEABLE_DATA(state, noteableDataMock);
expect(state.noteableData).toEqual(noteableDataMock);
});
});
......
......@@ -239,17 +239,31 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to be_nil }
end
<<<<<<< HEAD
context 'when kubernetes responds with valid pods' do
before do
stub_kubeclient_pods
end
it { is_expected.to eq(pods: [kube_pod]) }
=======
context 'when kubernetes responds with valid pods and deployments' do
before do
stub_kubeclient_pods
stub_kubeclient_deployments
end
it { is_expected.to eq(pods: [kube_pod], deployments: [kube_deployment]) }
>>>>>>> origin/master
end
context 'when kubernetes responds with 500s' do
before do
stub_kubeclient_pods(status: 500)
<<<<<<< HEAD
=======
stub_kubeclient_deployments(status: 500)
>>>>>>> origin/master
end
it { expect { subject }.to raise_error(KubeException) }
......@@ -258,9 +272,16 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
context 'when kubernetes responds with 404s' do
before do
stub_kubeclient_pods(status: 404)
<<<<<<< HEAD
end
it { is_expected.to eq(pods: []) }
=======
stub_kubeclient_deployments(status: 404)
end
it { is_expected.to eq(pods: [], deployments: []) }
>>>>>>> origin/master
end
end
end
......@@ -322,8 +322,8 @@ describe Environment do
end
end
describe '#deployment_service_ready?' do
subject { environment.deployment_service_ready? }
describe '#has_terminals?' do
subject { environment.has_terminals? }
context 'when the enviroment is available' do
context 'with a deployment service' do
......@@ -373,7 +373,7 @@ describe Environment do
context 'when the environment has terminals' do
before do
allow(environment).to receive(:deployment_service_ready?).and_return(true)
allow(environment).to receive(:has_terminals?).and_return(true)
end
shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
......@@ -386,6 +386,29 @@ describe Environment do
end
end
<<<<<<< HEAD
shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
it 'returns the terminals from the deployment service' do
expect(project.deployment_platform)
.to receive(:terminals).with(environment)
.and_return(:fake_terminals)
is_expected.to eq(:fake_terminals)
end
end
context 'when user configured kubernetes from Integration > Kubernetes' do
let(:project) { create(:kubernetes_project) }
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
context 'when user configured kubernetes from CI/CD > Clusters' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
=======
context 'when user configured kubernetes from Integration > Kubernetes' do
let(:project) { create(:kubernetes_project) }
......@@ -397,12 +420,13 @@ describe Environment do
let(:project) { cluster.project }
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
>>>>>>> origin/master
end
end
context 'when the environment does not have terminals' do
before do
allow(environment).to receive(:deployment_service_ready?).and_return(false)
allow(environment).to receive(:has_terminals?).and_return(false)
end
it { is_expected.to be_nil }
......@@ -410,13 +434,12 @@ describe Environment do
end
describe '#rollout_status' do
let(:project) { create(:kubernetes_project) }
shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do
subject { environment.rollout_status }
context 'when the environment has rollout status' do
before do
allow(environment).to receive(:deployment_service_ready?).and_return(true)
allow(environment).to receive(:has_terminals?).and_return(true)
end
it 'returns the rollout status from the deployment service' do
......@@ -428,15 +451,36 @@ describe Environment do
end
end
<<<<<<< HEAD
it 'returns the rollout status from the deployment service' do
expect(project.deployment_platform)
.to receive(:rollout_status).with(environment)
.and_return(:fake_rollout_status)
=======
context 'when the environment does not have rollout status' do
before do
allow(environment).to receive(:deployment_service_ready?).and_return(false)
allow(environment).to receive(:has_terminals?).and_return(false)
end
>>>>>>> origin/master
it { is_expected.to eq(nil) }
end
end
context 'when user configured kubernetes from Integration > Kubernetes' do
let(:project) { create(:kubernetes_project) }
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
context 'when user configured kubernetes from CI/CD > Clusters' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
end
describe '#has_metrics?' do
subject { environment.has_metrics? }
......
......@@ -51,7 +51,7 @@ describe EnvironmentEntity do
context 'with deployment service ready' do
before do
stub_licensed_features(deploy_board: true)
allow(environment).to receive(:deployment_service_ready?).and_return(true)
allow(environment).to receive(:has_terminals?).and_return(true)
allow(environment).to receive(:rollout_status).and_return(kube_deployment_rollout_status)
end
......@@ -63,7 +63,7 @@ describe EnvironmentEntity do
context 'when license does not has the GitLab_DeployBoard add-on' do
before do
stub_licensed_features(deploy_board: false)
allow(environment).to receive(:deployment_service_ready?).and_return(true)
allow(environment).to receive(:has_terminals?).and_return(true)
end
it 'does not expose rollout_status' do
......
......@@ -28,7 +28,7 @@ describe Geo::FileDownloadDispatchWorker, :geo, :truncate do
allow(Gitlab::Geo).to receive(:geo_database_configured?) { false }
expect(GeoFileDownloadWorker).not_to receive(:perform_async)
expect(Geo::FileDownloadWorker).not_to receive(:perform_async)
subject.perform
......@@ -43,7 +43,7 @@ describe Geo::FileDownloadDispatchWorker, :geo, :truncate do
secondary.enabled = false
secondary.save
expect(GeoFileDownloadWorker).not_to receive(:perform_async)
expect(Geo::FileDownloadWorker).not_to receive(:perform_async)
subject.perform
end
......@@ -58,8 +58,8 @@ describe Geo::FileDownloadDispatchWorker, :geo, :truncate do
end
it 'filters S3-backed files' do
expect(GeoFileDownloadWorker).to receive(:perform_async).with(:lfs, lfs_object_local_store.id)
expect(GeoFileDownloadWorker).not_to receive(:perform_async).with(:lfs, lfs_object_remote_store.id)
expect(Geo::FileDownloadWorker).to receive(:perform_async).with(:lfs, lfs_object_local_store.id)
expect(Geo::FileDownloadWorker).not_to receive(:perform_async).with(:lfs, lfs_object_remote_store.id)
subject.perform
end
......@@ -81,7 +81,7 @@ describe Geo::FileDownloadDispatchWorker, :geo, :truncate do
create_list(:upload, 2, :personal_snippet)
create(:appearance, logo: avatar, header_logo: avatar)
expect(GeoFileDownloadWorker).to receive(:perform_async).exactly(10).times.and_call_original
expect(Geo::FileDownloadWorker).to receive(:perform_async).exactly(10).times.and_call_original
# For 10 downloads, we expect four database reloads:
# 1. Load the first batch of 5.
# 2. 4 get sent out, 1 remains. This triggers another reload, which loads in the next 5.
......@@ -103,14 +103,14 @@ describe Geo::FileDownloadDispatchWorker, :geo, :truncate do
stub_const('Geo::BaseSchedulerWorker::DB_RETRIEVE_BATCH_SIZE', 1)
expect(GeoFileDownloadWorker).not_to receive(:perform_async).with(:lfs, failed_registry.file_id)
expect(GeoFileDownloadWorker).to receive(:perform_async).with(:lfs, unsynced.id)
expect(Geo::FileDownloadWorker).not_to receive(:perform_async).with(:lfs, failed_registry.file_id)
expect(Geo::FileDownloadWorker).to receive(:perform_async).with(:lfs, unsynced.id)
subject.perform
end
it 'retries failed files' do
expect(GeoFileDownloadWorker).to receive(:perform_async).with('lfs', failed_registry.file_id)
expect(Geo::FileDownloadWorker).to receive(:perform_async).with('lfs', failed_registry.file_id)
subject.perform
end
......@@ -118,7 +118,7 @@ describe Geo::FileDownloadDispatchWorker, :geo, :truncate do
it 'does not retries failed files when retry_at is tomorrow' do
failed_registry = create(:geo_file_registry, :lfs, file_id: 999, success: false, retry_at: Date.tomorrow)
expect(GeoFileDownloadWorker).not_to receive(:perform_async).with('lfs', failed_registry.file_id)
expect(Geo::FileDownloadWorker).not_to receive(:perform_async).with('lfs', failed_registry.file_id)
subject.perform
end
......@@ -126,7 +126,7 @@ describe Geo::FileDownloadDispatchWorker, :geo, :truncate do
it 'does not retries failed files when retry_at is in the past' do
failed_registry = create(:geo_file_registry, :lfs, file_id: 999, success: false, retry_at: Date.yesterday)
expect(GeoFileDownloadWorker).to receive(:perform_async).with('lfs', failed_registry.file_id)
expect(Geo::FileDownloadWorker).to receive(:perform_async).with('lfs', failed_registry.file_id)
subject.perform
end
......@@ -143,27 +143,27 @@ describe Geo::FileDownloadDispatchWorker, :geo, :truncate do
secondary.update_attribute(:namespaces, [synced_group])
end
it 'does not perform GeoFileDownloadWorker for LFS object that does not belong to selected namespaces to replicate' do
it 'does not perform Geo::FileDownloadWorker for LFS object that does not belong to selected namespaces to replicate' do
lfs_objec_in_synced_group = create(:lfs_objects_project, project: project_in_synced_group)
create(:lfs_objects_project, project: unsynced_project)
expect(GeoFileDownloadWorker).to receive(:perform_async)
expect(Geo::FileDownloadWorker).to receive(:perform_async)
.with(:lfs, lfs_objec_in_synced_group.lfs_object_id).once.and_return(spy)
subject.perform
end
it 'does not perform GeoFileDownloadWorker for upload objects that do not belong to selected namespaces to replicate' do
it 'does not perform Geo::FileDownloadWorker for upload objects that do not belong to selected namespaces to replicate' do
avatar = fixture_file_upload(Rails.root.join('spec/fixtures/dk.png'))
avatar_in_synced_group = create(:upload, model: synced_group, path: avatar)
create(:upload, model: create(:group), path: avatar)
avatar_in_project_in_synced_group = create(:upload, model: project_in_synced_group, path: avatar)
create(:upload, model: unsynced_project, path: avatar)
expect(GeoFileDownloadWorker).to receive(:perform_async)
expect(Geo::FileDownloadWorker).to receive(:perform_async)
.with('avatar', avatar_in_project_in_synced_group.id).once.and_return(spy)
expect(GeoFileDownloadWorker).to receive(:perform_async)
expect(Geo::FileDownloadWorker).to receive(:perform_async)
.with('avatar', avatar_in_synced_group.id).once.and_return(spy)
subject.perform
......
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