Commit 3f95d122 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into ce-to-ee-2018-01-16

parents fdc5a532 a107f5bd
...@@ -344,6 +344,10 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -344,6 +344,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Add group issue boards. - Add group issue boards.
- Ports style changes fixed in a conflict in ce to ee upstream to master for new projects page. - Ports style changes fixed in a conflict in ce to ee upstream to master for new projects page.
## 9.5.10 (2017-11-08)
- [SECURITY] Ensure GitLab Geo JSON web tokens expire after 2 minutes.
## 9.5.9 (2017-10-16) ## 9.5.9 (2017-10-16)
- [SECURITY] Prevent Related Issues from leaking confidential issues. - [SECURITY] Prevent Related Issues from leaking confidential issues.
......
...@@ -243,6 +243,9 @@ gem 'charlock_holmes', '~> 0.7.5' ...@@ -243,6 +243,9 @@ gem 'charlock_holmes', '~> 0.7.5'
# Faster JSON # Faster JSON
gem 'oj', '~> 2.17.4' gem 'oj', '~> 2.17.4'
# Faster blank
gem 'fast_blank'
# Parse time & duration # Parse time & duration
gem 'chronic', '~> 0.10.2' gem 'chronic', '~> 0.10.2'
gem 'chronic_duration', '~> 0.10.6' gem 'chronic_duration', '~> 0.10.6'
......
...@@ -231,6 +231,7 @@ GEM ...@@ -231,6 +231,7 @@ GEM
faraday_middleware-multi_json (0.0.6) faraday_middleware-multi_json (0.0.6)
faraday_middleware faraday_middleware
multi_json multi_json
fast_blank (1.0.0)
fast_gettext (1.4.0) fast_gettext (1.4.0)
ffaker (2.4.0) ffaker (2.4.0)
ffi (1.9.18) ffi (1.9.18)
...@@ -1069,6 +1070,7 @@ DEPENDENCIES ...@@ -1069,6 +1070,7 @@ DEPENDENCIES
factory_bot_rails (~> 4.8.2) factory_bot_rails (~> 4.8.2)
faraday (~> 0.12) faraday (~> 0.12)
faraday_middleware-aws-signers-v4 faraday_middleware-aws-signers-v4
fast_blank
ffaker (~> 2.4) ffaker (~> 2.4)
flay (~> 2.8.0) flay (~> 2.8.0)
flipper (~> 0.11.0) flipper (~> 0.11.0)
......
...@@ -31,7 +31,6 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; ...@@ -31,7 +31,6 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import UserCallout from './user_callout'; import UserCallout from './user_callout';
import ShortcutsWiki from './shortcuts_wiki'; import ShortcutsWiki from './shortcuts_wiki';
import BlobViewer from './blob/viewer/index'; import BlobViewer from './blob/viewer/index';
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import UsersSelect from './users_select'; import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete'; import GfmAutoComplete from './gfm_auto_complete';
import Star from './star'; import Star from './star';
...@@ -289,7 +288,6 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -289,7 +288,6 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
new LabelsSelect(); new LabelsSelect();
new MilestoneSelect(); new MilestoneSelect();
new IssuableTemplateSelectors(); new IssuableTemplateSelectors();
new AutoWidthDropdownSelect($('.js-target-branch-select')).init();
initApprovals(); initApprovals();
break; break;
...@@ -624,6 +622,8 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -624,6 +622,8 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
.catch(fail); .catch(fail);
break; break;
case 'projects:clusters:show': case 'projects:clusters:show':
case 'projects:clusters:update':
case 'projects:clusters:destroy':
import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle') import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle')
.then(cluster => new cluster.default()) // eslint-disable-line new-cap .then(cluster => new cluster.default()) // eslint-disable-line new-cap
.catch((err) => { .catch((err) => {
......
...@@ -11,6 +11,14 @@ class AutoWidthDropdownSelect { ...@@ -11,6 +11,14 @@ class AutoWidthDropdownSelect {
const dropdownClass = this.dropdownClass; const dropdownClass = this.dropdownClass;
this.$selectElement.select2({ this.$selectElement.select2({
dropdownCssClass: dropdownClass, dropdownCssClass: dropdownClass,
...AutoWidthDropdownSelect.selectOptions(this.dropdownClass),
});
return this;
}
static selectOptions(dropdownClass) {
return {
dropdownCss() { dropdownCss() {
let resultantWidth = 'auto'; let resultantWidth = 'auto';
const $dropdown = $(`.${dropdownClass}`); const $dropdown = $(`.${dropdownClass}`);
...@@ -29,9 +37,7 @@ class AutoWidthDropdownSelect { ...@@ -29,9 +37,7 @@ class AutoWidthDropdownSelect {
maxWidth: offsetParentWidth, maxWidth: offsetParentWidth,
}; };
}, },
}); };
return this;
} }
} }
......
...@@ -6,6 +6,7 @@ import Autosave from './autosave'; ...@@ -6,6 +6,7 @@ import Autosave from './autosave';
import UsersSelect from './users_select'; import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete'; import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode'; import ZenMode from './zen_mode';
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
import groupsSelect from './groups_select'; import groupsSelect from './groups_select';
...@@ -48,6 +49,12 @@ export default class IssuableForm { ...@@ -48,6 +49,12 @@ export default class IssuableForm {
}); });
calendar.setDate(parsePikadayDate($issuableDueDate.val())); calendar.setDate(parsePikadayDate($issuableDueDate.val()));
} }
this.$targetBranchSelect = $('.js-target-branch-select', this.form);
if (this.$targetBranchSelect.length) {
this.initTargetBranchDropdown();
}
} }
initAutosave() { initAutosave() {
...@@ -106,4 +113,37 @@ export default class IssuableForm { ...@@ -106,4 +113,37 @@ export default class IssuableForm {
addWip() { addWip() {
this.titleField.val(`WIP: ${(this.titleField.val())}`); this.titleField.val(`WIP: ${(this.titleField.val())}`);
} }
initTargetBranchDropdown() {
this.$targetBranchSelect.select2({
...AutoWidthDropdownSelect.selectOptions('js-target-branch-select'),
ajax: {
url: this.$targetBranchSelect.data('endpoint'),
dataType: 'JSON',
quietMillis: 250,
data(search) {
return {
search,
};
},
results(data) {
return {
// `data` keys are translated so we can't just access them with a string based key
results: data[Object.keys(data)[0]].map(name => ({
id: name,
text: name,
})),
};
},
},
initSelection(el, callback) {
const val = el.val();
callback({
id: val,
text: val,
});
},
});
}
} }
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
= f.hidden_field :source_project_id = f.hidden_field :source_project_id
= f.hidden_field :source_branch = f.hidden_field :source_branch
= f.hidden_field :target_project_id = f.hidden_field :target_project_id
= f.hidden_field :target_branch = f.hidden_field :target_branch, id: ''
.mr-compare.merge-request.js-merge-request-new-submit{ 'data-mr-submit-action': "#{j params[:tab].presence || 'new'}" } .mr-compare.merge-request.js-merge-request-new-submit{ 'data-mr-submit-action': "#{j params[:tab].presence || 'new'}" }
- if @commits.empty? - if @commits.empty?
......
...@@ -15,11 +15,10 @@ ...@@ -15,11 +15,10 @@
= form.label :target_branch, class: 'control-label' = form.label :target_branch, class: 'control-label'
.col-sm-10.target-branch-select-dropdown-container .col-sm-10.target-branch-select-dropdown-container
.issuable-form-select-holder .issuable-form-select-holder
= form.select(:target_branch, issuable.target_branches, = form.hidden_field(:target_branch,
{ include_blank: true },
{ class: 'target_branch js-target-branch-select ref-name', { class: 'target_branch js-target-branch-select ref-name',
disabled: issuable.new_record?, disabled: issuable.new_record?,
data: { placeholder: "Select branch" }}) data: { placeholder: "Select branch", endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }})
- if issuable.new_record? - if issuable.new_record?
   
= link_to 'Change branches', mr_change_branches_path(issuable) = link_to 'Change branches', mr_change_branches_path(issuable)
---
title: Fix JavaScript bundle running on Cluster update/destroy pages
merge_request: 4112
author:
type: fixed
---
title: Geo - Add a rake task to update Geo primary node URL
merge_request: 4097
author:
type: fixed
---
title: Make it possible to enable/disable PostgreSQL FDW for Geo
merge_request: 4020
author:
type: added
---
title: Add more endpoints for Geo Nodes API
merge_request: 3923
author:
type: added
---
title: "Add fast-blank"
merge_request: 16468
author:
type: performance
---
title: Improve performance of target branch dropdown
merge_request:
author:
type: performance
...@@ -9,6 +9,7 @@ production: ...@@ -9,6 +9,7 @@ production:
username: git username: git
password: "secure password" password: "secure password"
host: localhost host: localhost
fdw: true
# #
# Development specific # Development specific
...@@ -21,6 +22,7 @@ development: ...@@ -21,6 +22,7 @@ development:
username: postgres username: postgres
password: "secure password" password: "secure password"
host: localhost host: localhost
fdw: true
# #
# Staging specific # Staging specific
...@@ -33,6 +35,7 @@ staging: ...@@ -33,6 +35,7 @@ staging:
username: git username: git
password: "secure password" password: "secure password"
host: localhost host: localhost
fdw: true
# Warning: The database defined as "test" will be erased and # Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake". # re-generated from your development database when you run "rake".
...@@ -45,3 +48,4 @@ test: &test ...@@ -45,3 +48,4 @@ test: &test
username: postgres username: postgres
password: password:
host: localhost host: localhost
fdw: true
...@@ -65,6 +65,60 @@ Example response: ...@@ -65,6 +65,60 @@ Example response:
} }
``` ```
## Edit a Geo node
Updates an existing Geo secondary node. The primary node cannot be edited.
```
PUT /geo_nodes/:id
```
| Attribute | Type | Required | Description |
|----------------------|---------|----------|---------------------------------------------------------------------------|
| `id` | integer | yes | The ID of the Geo node. |
| `enabled` | boolean | no | Flag indicating if the Geo node is enabled. |
| `url` | string | no | The URL to connect to the Geo node. |
| `files_max_capacity` | integer | no | Control the maximum concurrency of LFS/attachment backfill for this node. |
| `repos_max_capacity` | integer | no | Control the maximum concurrency of repository backfill for this node. |
Example response:
```json
{
"id": 1,
"url": "https://primary.example.com/",
"primary": true,
"enabled": true,
"current": true,
"files_max_capacity": 10,
"repos_max_capacity": 25,
"clone_protocol": "http"
}
```
## Repair a Geo node
To repair the OAuth authentication of a Geo node.
```
PUT /geo_nodes/:id/repair
```
Example response:
```json
{
"id": 1,
"url": "https://primary.example.com/",
"primary": true,
"enabled": true,
"current": true,
"files_max_capacity": 10,
"repos_max_capacity": 25,
"clone_protocol": "http"
}
```
## Retrieve status about all secondary Geo nodes ## Retrieve status about all secondary Geo nodes
``` ```
......
...@@ -39,9 +39,9 @@ recover. See below for more details. ...@@ -39,9 +39,9 @@ recover. See below for more details.
The following guide assumes that: The following guide assumes that:
- You are using Omnibus and therefore you are using PostgreSQL 9.6 or later - You are using Omnibus and therefore you are using PostgreSQL 9.6 or later
which includes the [`pg_basebackup` tool][pgback] and improved which includes the [`pg_basebackup` tool][pgback] and improved
[Foreign Data Wrapper][FDW] support. [Foreign Data Wrapper][FDW] support.
- You have a primary node already set up (the GitLab server you are - You have a primary node already set up (the GitLab server you are
replicating from), running Omnibus' PostgreSQL (or equivalent version), and replicating from), running Omnibus' PostgreSQL (or equivalent version), and
you have a new secondary server set up with the same versions of the OS, you have a new secondary server set up with the same versions of the OS,
...@@ -67,22 +67,22 @@ The following guide assumes that: ...@@ -67,22 +67,22 @@ The following guide assumes that:
``` ```
This command will use your defined `external_url` in `/etc/gitlab/gitlab.rb`. This command will use your defined `external_url` in `/etc/gitlab/gitlab.rb`.
1. GitLab 10.4 and up only: Do the following to make sure the `gitlab` database user has a password defined 1. GitLab 10.4 and up only: Do the following to make sure the `gitlab` database user has a password defined
Generate a MD5 hash of the desired password: Generate a MD5 hash of the desired password:
```bash ```bash
gitlab-ctl pg-password-md5 gitlab gitlab-ctl pg-password-md5 gitlab
# Enter password: mypassword # Enter password: mypassword
# Confirm password: mypassword # Confirm password: mypassword
# fca0b89a972d69f00eb3ec98a5838484 # fca0b89a972d69f00eb3ec98a5838484
``` ```
Edit `/etc/gitlab/gitlab.rb`: Edit `/etc/gitlab/gitlab.rb`:
```ruby ```ruby
# Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab` # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab`
postgresql['sql_user_password'] = 'fca0b89a972d69f00eb3ec98a5838484' postgresql['sql_user_password'] = 'fca0b89a972d69f00eb3ec98a5838484'
# If you have HA setup, this must be present in all nodes as well # If you have HA setup, this must be present in all nodes as well
...@@ -304,25 +304,25 @@ because we have not yet configured the secondary server. This is the next step. ...@@ -304,25 +304,25 @@ because we have not yet configured the secondary server. This is the next step.
connections. The certificate can only be replicated by someone with access connections. The certificate can only be replicated by someone with access
to the private key, which is **only** present on the primary node. to the private key, which is **only** present on the primary node.
1. Configure PostgreSQL to listen on network interfaces on secondary 1. Configure PostreSQL to enable FDW support
This step is similar to how we configured the primary instance. This step is similar to how we configured the primary instance.
We need to enable this, even if using a single node, to enable FDW support. We need to enable this, to enable FDW support, even if using a single node.
Edit `/etc/gitlab/gitlab.rb` and add the following, replacing the IP Edit `/etc/gitlab/gitlab.rb` and add the following, replacing the IP
addresses with addresses appropriate to your network configuration: addresses with addresses appropriate to your network configuration:
```ruby ```ruby
geo_primary_role['enable'] = true
# Secondary addresses # Secondary addresses
# - replace '5.6.7.8' with the secondary public address # - replace '5.6.7.8' with the secondary private address
postgresql['listen_address'] = '5.6.7.8' postgresql['listen_address'] = '5.6.7.8'
postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','5.6.7.8/32'] postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','5.6.7.8/32']
postgresql['md5_auth_cidr_addresses'] = ['5.6.7.8/32']
# gitlab database user's password (defined previously) # gitlab database user's password (defined previously)
gitlab_rails['db_password'] = 'mypassword' gitlab_rails['db_password'] = 'mypassword'
# enable fdw for the geo tracking database
geo_secondary['db_fdw'] = true
``` ```
1. Test that the `gitlab-psql` user can connect to the primary's database: 1. Test that the `gitlab-psql` user can connect to the primary's database:
...@@ -477,4 +477,5 @@ Read the [troubleshooting document](troubleshooting.md). ...@@ -477,4 +477,5 @@ Read the [troubleshooting document](troubleshooting.md).
[pgback]: http://www.postgresql.org/docs/9.2/static/app-pgbasebackup.html [pgback]: http://www.postgresql.org/docs/9.2/static/app-pgbasebackup.html
[external postgresql]: #external-postgresql-instances [external postgresql]: #external-postgresql-instances
[tracking]: database_source.md#enable-tracking-database-on-the-secondary-server [tracking]: database_source.md#enable-tracking-database-on-the-secondary-server
[FDW]: https://www.postgresql.org/docs/9.6/static/postgres-fdw.html
[toc]: README.md#using-omnibus-gitlab [toc]: README.md#using-omnibus-gitlab
...@@ -33,9 +33,8 @@ recover. See below for more details. ...@@ -33,9 +33,8 @@ recover. See below for more details.
The following guide assumes that: The following guide assumes that:
- You are using PostgreSQL 9.6 or later - You are using PostgreSQL 9.6 or later which includes the
which includes the [`pg_basebackup` tool][pgback] and improved [Foreign Data Wrapper][FDW] support.
[`pg_basebackup` tool][pgback] and improved [Foreign Data Wrapper][FDW] support.
- You have a primary node already set up (the GitLab server you are - You have a primary node already set up (the GitLab server you are
replicating from), running PostgreSQL 9.6 or later, and replicating from), running PostgreSQL 9.6 or later, and
you have a new secondary server set up with the same versions of the OS, you have a new secondary server set up with the same versions of the OS,
...@@ -291,6 +290,9 @@ node. ...@@ -291,6 +290,9 @@ node.
sudo -u postgres psql -h $GEO_DB_HOST -d $GEO_DB_NAME -p $GEO_DB_PORT -c "GRANT USAGE ON FOREIGN SERVER gitlab_secondary TO $(GEO_DB_USER);" sudo -u postgres psql -h $GEO_DB_HOST -d $GEO_DB_NAME -p $GEO_DB_PORT -c "GRANT USAGE ON FOREIGN SERVER gitlab_secondary TO $(GEO_DB_USER);"
``` ```
And edit the content of `database_geo.yml` and to add `fdw: true` to
the `production:` block.
### Step 4. Initiate the replication process ### Step 4. Initiate the replication process
Below we provide a script that connects the database on the secondary node to Below we provide a script that connects the database on the secondary node to
......
...@@ -9,7 +9,7 @@ fail-over with minimal effort, in a disaster situation. ...@@ -9,7 +9,7 @@ fail-over with minimal effort, in a disaster situation.
See [current limitations](README.md#current-limitations) for more information. See [current limitations](README.md#current-limitations) for more information.
## Promoting a secondary geo replica ### Step 1. Promoting a secondary geo replica
> **Warning:** Disaster Recovery does not yet support systems with multiple > **Warning:** Disaster Recovery does not yet support systems with multiple
> secondary geo replicas (e.g. one primary and two or more secondaries). > secondary geo replicas (e.g. one primary and two or more secondaries).
...@@ -42,20 +42,6 @@ It does not enable GitLab Geo on the newly promoted primary. ...@@ -42,20 +42,6 @@ It does not enable GitLab Geo on the newly promoted primary.
sudo -i sudo -i
``` ```
1. Optional: Update the primary domain's DNS record.
Updating the DNS records for the primary domain to point to the secondary
will prevent the need to update all references to the primary domain to the
secondary domain, like changing Git remotes and API URLs.
After updating the primary domain's DNS records to point to the secondary,
edit `/etc/gitlab/gitlab.rb` on the the secondary to reflect the new URL:
```
# Change the existing external_url configuration
external_url 'https://gitlab.example.com'
```
1. Edit `/etc/gitlab/gitlab.rb` to reflect its new status as primary. 1. Edit `/etc/gitlab/gitlab.rb` to reflect its new status as primary.
Remove the following line: Remove the following line:
...@@ -79,10 +65,49 @@ It does not enable GitLab Geo on the newly promoted primary. ...@@ -79,10 +65,49 @@ It does not enable GitLab Geo on the newly promoted primary.
previously for the secondary. previously for the secondary.
1. Success! The secondary has now been promoted to primary. 1. Success! The secondary has now been promoted to primary.
### Step 2. (Optional) Updating the primary domain's DNS record
Updating the DNS records for the primary domain to point to the secondary
will prevent the need to update all references to the primary domain to the
secondary domain, like changing Git remotes and API URLs.
1. SSH in to your **secondary** and login as root:
```
sudo -i
```
1. Update the primary domain's DNS record.
After updating the primary domain's DNS records to point to the secondary,
edit `/etc/gitlab/gitlab.rb` on the the secondary to reflect the new URL:
```
# Change the existing external_url configuration
external_url 'https://gitlab.example.com'
```
1. Reconfigure the secondary node for the change to take effect:
```
gitlab-ctl reconfigure
```
1. Execute the command below to update the newly promoted primary node URL:
```
gitlab-rake geo:update_primary_node_url
```
This command will use the changed `external_url` configuration defined
in `/etc/gitlab/gitlab.rb`.
1. Verify you can connect to the newly promoted primary using the primary URL.
If you updated the DNS records for the primary domain, these changes may If you updated the DNS records for the primary domain, these changes may
not have yet propagated depending on the previous DNS records TTL. not have yet propagated depending on the previous DNS records TTL.
## Add secondary geo replicas to a promoted primary ### Step 3. (Optional) Add secondary geo replicas to a promoted primary
Promoting a secondary to primary using the process above does not enable Promoting a secondary to primary using the process above does not enable
GitLab Geo on the new primary. GitLab Geo on the new primary.
......
# Issue Weight # Issue Weight
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/76)
in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/) 8.3.
When you have a lot of issues, it can be hard to get an overview. When you have a lot of issues, it can be hard to get an overview.
By adding a weight to each issue, you can get a better idea of how much time, By adding a weight to each issue, you can get a better idea of how much time,
value or complexity a given issue has or will cost. value or complexity a given issue has or will cost.
......
...@@ -309,6 +309,23 @@ It may be possible to implement a locking mechanism using the server-side ...@@ -309,6 +309,23 @@ It may be possible to implement a locking mechanism using the server-side
`pre-receive` hook to prevent the race condition. Read about [configuring `pre-receive` hook to prevent the race condition. Read about [configuring
custom Git hooks][hooks] on the GitLab server. custom Git hooks][hooks] on the GitLab server.
### Mirroring with Perforce via GitFusion
> **Warning:** Bidirectional mirroring should not be used as a permanent
> configuration. There is no bidirectional mirroring without conflicts.
> Refer to [Migrating from Perforce Helix][perforce] for alternative migration
> approaches.
GitFusion provides a Git interface to Perforce which can be used by GitLab to
bidirectionally mirror projects with GitLab. This may be useful in some
situations when migrating from Perforce to GitLab where overlapping Perforce
workspaces cannot be migrated simultaneously to GitLab.
If using mirroring with Perforce you should only mirror protected branches.
Perforce will reject any pushes that rewrite history. It is recommended that
only the fewest number of branches are mirrored due to the performance
limitations of GitFusion.
[ee-51]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51 [ee-51]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51
[ee-2551]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551 [ee-2551]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551
[ee-3117]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117 [ee-3117]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117
...@@ -320,3 +337,4 @@ custom Git hooks][hooks] on the GitLab server. ...@@ -320,3 +337,4 @@ custom Git hooks][hooks] on the GitLab server.
[deploy-key]: ../ssh/README.md#deploy-keys [deploy-key]: ../ssh/README.md#deploy-keys
[webhook]: ../user/project/integrations/webhooks.html#push-events [webhook]: ../user/project/integrations/webhooks.html#push-events
[pull-api]: ../api/projects.html#start-the-pull-mirroring-process-for-a-project [pull-api]: ../api/projects.html#start-the-pull-mirroring-process-for-a-project
[perforce]: ../user/project/import/perforce.html
...@@ -44,9 +44,9 @@ class Admin::GeoNodesController < Admin::ApplicationController ...@@ -44,9 +44,9 @@ class Admin::GeoNodesController < Admin::ApplicationController
end end
def repair def repair
if @node.primary? || !@node.missing_oauth_application? if !@node.missing_oauth_application?
flash[:notice] = "This node doesn't need to be repaired." flash[:notice] = "This node doesn't need to be repaired."
elsif @node.save elsif @node.repair
flash[:notice] = 'Node Authentication was successfully repaired.' flash[:notice] = 'Node Authentication was successfully repaired.'
else else
flash[:alert] = 'There was a problem repairing Node Authentication.' flash[:alert] = 'There was a problem repairing Node Authentication.'
......
...@@ -26,6 +26,8 @@ class GeoNode < ActiveRecord::Base ...@@ -26,6 +26,8 @@ class GeoNode < ActiveRecord::Base
before_validation :ensure_access_keys! before_validation :ensure_access_keys!
alias_method :repair, :save # the `update_dependents_attributes` hook will take care of it
scope :with_url_prefix, ->(prefix) { where('url LIKE ?', "#{prefix}%") } scope :with_url_prefix, ->(prefix) { where('url LIKE ?', "#{prefix}%") }
attr_encrypted :secret_access_key, attr_encrypted :secret_access_key,
......
class GeoNodeStatusEntity < Grape::Entity
include ActionView::Helpers::NumberHelper
expose :geo_node_id
expose :healthy?, as: :healthy
expose :health do |node|
node.healthy? ? 'Healthy' : node.health
end
expose :health_status
expose :missing_oauth_application, as: :missing_oauth_application
expose :attachments_count
expose :attachments_synced_count
expose :attachments_failed_count
expose :attachments_synced_in_percentage do |node|
number_to_percentage(node.attachments_synced_in_percentage, precision: 2)
end
expose :db_replication_lag_seconds
expose :lfs_objects_count
expose :lfs_objects_synced_count
expose :lfs_objects_failed_count
expose :lfs_objects_synced_in_percentage do |node|
number_to_percentage(node.lfs_objects_synced_in_percentage, precision: 2)
end
expose :job_artifacts_count
expose :job_artifacts_synced_count
expose :job_artifacts_failed_count
expose :job_artifacts_synced_in_percentage do |node|
number_to_percentage(node.job_artifacts_synced_in_percentage, precision: 2)
end
expose :repositories_count
expose :repositories_failed_count
expose :repositories_synced_count
expose :repositories_synced_in_percentage do |node|
number_to_percentage(node.repositories_synced_in_percentage, precision: 2)
end
expose :wikis_count
expose :wikis_failed_count
expose :wikis_synced_count
expose :wikis_synced_in_percentage do |node|
number_to_percentage(node.wikis_synced_in_percentage, precision: 2)
end
expose :replication_slots_count
expose :replication_slots_used_count
expose :replication_slots_used_in_percentage do |node|
number_to_percentage(node.replication_slots_used_in_percentage, precision: 2)
end
expose :replication_slots_max_retained_wal_bytes
expose :last_event_id
expose :last_event_timestamp
expose :cursor_last_event_id
expose :cursor_last_event_timestamp
expose :last_successful_status_check_timestamp
expose :version
expose :revision
expose :namespaces, using: NamespaceEntity
# We load GeoNodeStatus data in two ways:
#
# 1. Directly by asking a Geo node via an API call
# 2. Via cached state in the database
#
# We don't yet cached the state of the shard information in the database, so if
# we don't have this information omit from the serialization entirely.
expose :storage_shards, using: StorageShardEntity, if: ->(status, options) do
status.storage_shards.present?
end
expose :storage_shards_match?, as: :storage_shards_match, if: -> (status, options) do
Gitlab::Geo.primary? && status.storage_shards.present?
end
private
def namespaces
object.geo_node.namespaces
end
def missing_oauth_application
object.geo_node.missing_oauth_application?
end
def version
Gitlab::VERSION
end
def revision
Gitlab::REVISION
end
end
class GeoNodeStatusSerializer < BaseSerializer class GeoNodeStatusSerializer < BaseSerializer
entity GeoNodeStatusEntity entity API::Entities::GeoNodeStatus
end end
...@@ -65,14 +65,22 @@ module Gitlab ...@@ -65,14 +65,22 @@ module Gitlab
::License.feature_available?(:geo) ::License.feature_available?(:geo)
end end
def self.fdw? def self.fdw_capable?
self.cache_value(:geo_fdw?) do self.cache_value(:geo_fdw_capable?) do
::Geo::BaseRegistry.connection.execute( ::Geo::TrackingBase.connection.execute(
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '#{FDW_SCHEMA}' AND table_type = 'FOREIGN TABLE'" "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '#{FDW_SCHEMA}' AND table_type = 'FOREIGN TABLE'"
).first.fetch('count').to_i.positive? ).first.fetch('count').to_i.positive?
end end
end end
def self.fdw?
return false unless self.fdw_capable?
# FDW is enabled by default, disable it by setting `fdw: false` in config/database_geo.yml
value = Rails.configuration.geo_database['fdw']
value.nil? ? true : value
end
def self.fdw_table(table_name) def self.fdw_table(table_name)
FDW_SCHEMA + ".#{table_name}" FDW_SCHEMA + ".#{table_name}"
end end
......
...@@ -15,6 +15,26 @@ module Gitlab ...@@ -15,6 +15,26 @@ module Gitlab
end end
end end
def update_primary_geo_node_url
node = Gitlab::Geo.primary_node
unless node.present?
$stdout.puts 'This is not a primary node'.color(:red)
exit 1
end
$stdout.puts "Updating primary Geo node with URL #{node.url} ..."
if node.update(url: GeoNode.current_node_url)
puts "#{node.url} is now the primary Geo node URL".color(:green)
$stdout.puts "#{node.url} is now the primary Geo node URL".color(:green)
else
puts "Error saving Geo node:\n#{node.errors.full_messages.join("\n")}".color(:red)
$stdout.puts "Error saving Geo node:\n#{node.errors.full_messages.join("\n")}".color(:red)
exit 1
end
end
def refresh_foreign_tables! def refresh_foreign_tables!
sql = <<~SQL sql = <<~SQL
DROP SCHEMA IF EXISTS gitlab_secondary CASCADE; DROP SCHEMA IF EXISTS gitlab_secondary CASCADE;
......
...@@ -1130,6 +1130,8 @@ module API ...@@ -1130,6 +1130,8 @@ module API
end end
class GeoNode < Grape::Entity class GeoNode < Grape::Entity
include ::API::Helpers::RelatedResourcesHelpers
expose :id expose :id
expose :url expose :url
expose :primary?, as: :primary expose :primary?, as: :primary
...@@ -1142,6 +1144,129 @@ module API ...@@ -1142,6 +1144,129 @@ module API
expose :clone_protocol do |_record, _options| expose :clone_protocol do |_record, _options|
'http' 'http'
end end
expose :_links do
expose :self do |geo_node|
expose_url api_v4_geo_nodes_path(id: geo_node.id)
end
expose :repair do |geo_node|
expose_url api_v4_geo_nodes_repair_path(id: geo_node.id)
end
end
end
class GeoNodeStatus < Grape::Entity
include ::API::Helpers::RelatedResourcesHelpers
include ActionView::Helpers::NumberHelper
expose :geo_node_id
expose :healthy?, as: :healthy
expose :health do |node|
node.healthy? ? 'Healthy' : node.health
end
expose :health_status
expose :missing_oauth_application
expose :attachments_count
expose :attachments_synced_count
expose :attachments_failed_count
expose :attachments_synced_in_percentage do |node|
number_to_percentage(node.attachments_synced_in_percentage, precision: 2)
end
expose :db_replication_lag_seconds
expose :lfs_objects_count
expose :lfs_objects_synced_count
expose :lfs_objects_failed_count
expose :lfs_objects_synced_in_percentage do |node|
number_to_percentage(node.lfs_objects_synced_in_percentage, precision: 2)
end
expose :job_artifacts_count
expose :job_artifacts_synced_count
expose :job_artifacts_failed_count
expose :job_artifacts_synced_in_percentage do |node|
number_to_percentage(node.job_artifacts_synced_in_percentage, precision: 2)
end
expose :repositories_count
expose :repositories_failed_count
expose :repositories_synced_count
expose :repositories_synced_in_percentage do |node|
number_to_percentage(node.repositories_synced_in_percentage, precision: 2)
end
expose :wikis_count
expose :wikis_failed_count
expose :wikis_synced_count
expose :wikis_synced_in_percentage do |node|
number_to_percentage(node.wikis_synced_in_percentage, precision: 2)
end
expose :replication_slots_count
expose :replication_slots_used_count
expose :replication_slots_used_in_percentage do |node|
number_to_percentage(node.replication_slots_used_in_percentage, precision: 2)
end
expose :replication_slots_max_retained_wal_bytes
expose :last_event_id
expose :last_event_timestamp
expose :cursor_last_event_id
expose :cursor_last_event_timestamp
expose :last_successful_status_check_timestamp
expose :version
expose :revision
expose :namespaces, using: NamespaceBasic
# We load GeoNodeStatus data in two ways:
#
# 1. Directly by asking a Geo node via an API call
# 2. Via cached state in the database
#
# We don't yet cached the state of the shard information in the database, so if
# we don't have this information omit from the serialization entirely.
expose :storage_shards, using: StorageShardEntity, if: ->(status, options) do
status.storage_shards.present?
end
expose :storage_shards_match?, as: :storage_shards_match, if: -> (status, options) do
Gitlab::Geo.primary? && status.storage_shards.present?
end
expose :_links do
expose :self do |geo_node_status|
expose_url api_v4_geo_nodes_status_path(id: geo_node_status.geo_node_id)
end
expose :node do |geo_node_status|
expose_url api_v4_geo_nodes_path(id: geo_node_status.geo_node_id)
end
end
private
def namespaces
object.geo_node.namespaces
end
def missing_oauth_application
object.geo_node.missing_oauth_application?
end
def version
Gitlab::VERSION
end
def revision
Gitlab::REVISION
end
end end
class PersonalAccessToken < Grape::Entity class PersonalAccessToken < Grape::Entity
......
...@@ -36,7 +36,7 @@ module API ...@@ -36,7 +36,7 @@ module API
authenticate_by_gitlab_geo_node_token! authenticate_by_gitlab_geo_node_token!
status = ::GeoNodeStatus.current_node_status status = ::GeoNodeStatus.current_node_status
present status, with: GeoNodeStatusEntity present status, with: Entities::GeoNodeStatus
end end
end end
......
...@@ -2,6 +2,7 @@ module API ...@@ -2,6 +2,7 @@ module API
class GeoNodes < Grape::API class GeoNodes < Grape::API
include PaginationParams include PaginationParams
include APIGuard include APIGuard
include ::Gitlab::Utils::StrongMemoize
before { authenticated_as_admin! } before { authenticated_as_admin! }
...@@ -25,12 +26,12 @@ module API ...@@ -25,12 +26,12 @@ module API
# Example request: # Example request:
# GET /geo_nodes/status # GET /geo_nodes/status
desc 'Get status for all Geo nodes' do desc 'Get status for all Geo nodes' do
success GeoNodeStatusEntity success Entities::GeoNodeStatus
end end
get '/status' do get '/status' do
status = GeoNodeStatus.all status = GeoNodeStatus.all
present paginate(status), with: GeoNodeStatusEntity present paginate(status), with: Entities::GeoNodeStatus
end end
# Get project registry failures for the current Geo node # Get project registry failures for the current Geo node
...@@ -55,49 +56,95 @@ module API ...@@ -55,49 +56,95 @@ module API
present project_registries, with: ::GeoProjectRegistryEntity present project_registries, with: ::GeoProjectRegistryEntity
end end
# Get all Geo node information route_param :id, type: Integer, desc: 'The ID of the node' do
# helpers do
# Example request: def geo_node
# GET /geo_nodes/:id strong_memoize(:geo_node) { GeoNode.find(params[:id]) }
desc 'Get a single GeoNode' do end
success Entities::GeoNode
end
params do
requires :id, type: Integer, desc: 'The ID of the node'
end
get ':id' do
node = GeoNode.find_by(id: params[:id])
not_found!('GeoNode') unless node
present node, with: Entities::GeoNode
end
# Get Geo metrics for a single node
#
# Example request:
# GET /geo_nodes/:id/status
desc 'Get metrics for a single Geo node' do
success Entities::GeoNode
end
params do
requires :id, type: Integer, desc: 'The ID of the node'
end
get ':id/status' do
geo_node = GeoNode.find(params[:id])
not_found('Geo node not found') unless geo_node
status = def geo_node_status
if geo_node.current? strong_memoize(:geo_node_status) do
GeoNodeStatus.current_node_status if geo_node.current?
GeoNodeStatus.current_node_status
else
geo_node.status
end
end
end
end
# Get all Geo node information
#
# Example request:
# GET /geo_nodes/:id
desc 'Get a single GeoNode' do
success Entities::GeoNode
end
get do
not_found!('GeoNode') unless geo_node
present geo_node, with: Entities::GeoNode
end
# Get Geo metrics for a single node
#
# Example request:
# GET /geo_nodes/:id/status
desc 'Get metrics for a single Geo node' do
success Entities::GeoNodeStatus
end
get 'status' do
not_found!('GeoNode') unless geo_node
not_found!('Status for Geo node not found') unless geo_node_status
present geo_node_status, with: Entities::GeoNodeStatus
end
# Repair authentication of the Geo node
#
# Example request:
# POST /geo_nodes/:id/repair
desc 'Repair authentication of the Geo node' do
success Entities::GeoNodeStatus
end
post 'repair' do
not_found!('GeoNode') unless geo_node
if !geo_node.missing_oauth_application? || geo_node.repair
status 200
present geo_node_status, with: Entities::GeoNodeStatus
else else
geo_node.status render_validation_error!(geo_node)
end end
end
not_found!('Status for Geo node not found') unless status
# Edit an existing Geo node
present status, with: ::GeoNodeStatusEntity #
# Example request:
# PUT /geo_nodes/:id
desc 'Edit an existing Geo secondary node' do
success Entities::GeoNode
end
params do
optional :enabled, type: Boolean, desc: 'Flag indicating if the Geo node is enabled'
optional :url, type: String, desc: 'The URL to connect to the Geo node'
optional :files_max_capacity, type: Integer, desc: 'Control the maximum concurrency of LFS/attachment backfill for this secondary node'
optional :repos_max_capacity, type: Integer, desc: 'Control the maximum concurrency of repository backfill for this secondary node'
end
put do
not_found!('GeoNode') unless geo_node
update_params = declared_params(include_missing: false)
if geo_node.primary?
forbidden!('Primary node cannot be edited')
elsif geo_node.update_attributes(update_params)
present geo_node, with: Entities::GeoNode
else
render_validation_error!(geo_node)
end
end
end end
end end
end end
......
...@@ -38,6 +38,7 @@ module API ...@@ -38,6 +38,7 @@ module API
builds = user_project.builds.order('id DESC') builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope]) builds = filter_builds(builds, params[:scope])
builds = builds.preload(:user, :job_artifacts_archive, :runner, pipeline: :project)
present paginate(builds), with: Entities::Job present paginate(builds), with: Entities::Job
end end
......
...@@ -36,6 +36,7 @@ module API ...@@ -36,6 +36,7 @@ module API
builds = user_project.builds.order('id DESC') builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope]) builds = filter_builds(builds, params[:scope])
builds = builds.preload(:user, :job_artifacts_archive, :runner, pipeline: :project)
present paginate(builds), with: ::API::V3::Entities::Build present paginate(builds), with: ::API::V3::Entities::Build
end end
......
...@@ -194,4 +194,11 @@ namespace :geo do ...@@ -194,4 +194,11 @@ namespace :geo do
current_node.update!(primary: true) current_node.update!(primary: true)
end end
end end
desc 'Update Geo primary node URL'
task update_primary_node_url: :environment do
abort GEO_LICENSE_ERROR_TEXT unless Gitlab::Geo.license_allows?
Gitlab::Geo::GeoTasks.update_primary_geo_node_url
end
end end
#!/bin/sh
# Check if file exists with -f. Check if in in the gdk rook directory.
if [ ! -f ../GDK_ROOT ]; then
echo "Please run script from gitlab (e.g. gitlab-development-kit/gitlab) root directory."
exit 1
fi
PRECOMMIT=$(git rev-parse --git-dir)/hooks/pre-commit
# Check if symlink exists with -L. Check if script was already installed.
if [ -L $PRECOMMIT ]; then
echo "Pre-commit script already installed."
exit 1
fi
ln -s ./pre-commit $PRECOMMIT
echo "Pre-commit script installed successfully"
#!/bin/sh
# Check if file exists with -f. Check if in in the gdk rook directory.
if [ ! -f ../GDK_ROOT ]; then
echo "Please run pre-commit from gitlab (e.g. gitlab-development-kit/gitlab) root directory."
exit 1
fi
jsfiles=$(git diff --cached --name-only --diff-filter=ACM "*.js" | tr '\n' ' ')
[ -z "$jsfiles" ] && exit 0
# Prettify all staged .js files
echo "$jsfiles" | xargs ./node_modules/.bin/prettier --write
# Add back the modified/prettified files to staging
echo "$jsfiles" | xargs git add
exit 0
...@@ -8,3 +8,8 @@ CREATE SCHEMA gitlab_secondary; ...@@ -8,3 +8,8 @@ CREATE SCHEMA gitlab_secondary;
IMPORT FOREIGN SCHEMA public FROM SERVER gitlab_secondary INTO gitlab_secondary; IMPORT FOREIGN SCHEMA public FROM SERVER gitlab_secondary INTO gitlab_secondary;
GRANT USAGE ON FOREIGN SERVER gitlab_secondary TO current_user; GRANT USAGE ON FOREIGN SERVER gitlab_secondary TO current_user;
EOF EOF
# Ensure the FDW setting is enabled
sed -i '/fdw:/d' config/database_geo.yml
sed -i '/gitlabhq_geo_test/a\
\ \ fdw: true' config/database_geo.yml
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
"current", "current",
"files_max_capacity", "files_max_capacity",
"repos_max_capacity", "repos_max_capacity",
"clone_protocol" "clone_protocol",
"_links"
], ],
"properties" : { "properties" : {
"id": { "type": "integer" }, "id": { "type": "integer" },
...@@ -18,7 +19,16 @@ ...@@ -18,7 +19,16 @@
"current": { "type": "boolean" }, "current": { "type": "boolean" },
"files_max_capacity": { "type": "integer" }, "files_max_capacity": { "type": "integer" },
"repos_max_capacity": { "type": "integer" }, "repos_max_capacity": { "type": "integer" },
"clone_protocol": { "type": ["string"] } "clone_protocol": { "type": ["string"] },
"_links": {
"type": "object",
"required": ["self", "repair"],
"properties" : {
"self": { "type": "string" },
"repair": { "type": "string" }
},
"additionalProperties": false
}
}, },
"additionalProperties": false "additionalProperties": false
} }
...@@ -32,7 +32,8 @@ ...@@ -32,7 +32,8 @@
"cursor_last_event_timestamp", "cursor_last_event_timestamp",
"namespaces", "namespaces",
"version", "version",
"revision" "revision",
"_links"
], ],
"properties" : { "properties" : {
"geo_node_id": { "type": "integer" }, "geo_node_id": { "type": "integer" },
...@@ -74,7 +75,16 @@ ...@@ -74,7 +75,16 @@
"storage_shards": { "type": "array" }, "storage_shards": { "type": "array" },
"storage_shards_match": { "type": "boolean" }, "storage_shards_match": { "type": "boolean" },
"version": { "type": ["string"] }, "version": { "type": ["string"] },
"revision": { "type": ["string"] } "revision": { "type": ["string"] },
"_links": {
"type": "object",
"required": ["self", "node"],
"properties" : {
"self": { "type": "string" },
"node": { "type": "string" }
},
"additionalProperties": false
}
}, },
"additionalProperties": false "additionalProperties": false
} }
...@@ -267,7 +267,7 @@ describe Admin::GeoNodesController, :postgresql do ...@@ -267,7 +267,7 @@ describe Admin::GeoNodesController, :postgresql do
it 'returns the status' do it 'returns the status' do
get :status, id: geo_node, format: :json get :status, id: geo_node, format: :json
expect(response).to match_response_schema('geo_node_status') expect(response).to match_response_schema('public_api/v4/geo_node_status', dir: 'ee')
end end
end end
end end
......
...@@ -18,24 +18,57 @@ describe Gitlab::Geo, :geo do ...@@ -18,24 +18,57 @@ describe Gitlab::Geo, :geo do
end end
end end
describe 'fdw?' do describe 'fdw_capable?' do
let(:fdw_check) { "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'gitlab_secondary' AND table_type = 'FOREIGN TABLE'" } let(:fdw_check) { "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'gitlab_secondary' AND table_type = 'FOREIGN TABLE'" }
before do before do
allow(::Geo::BaseRegistry.connection).to receive(:execute).with(anything).and_call_original allow(::Geo::TrackingBase.connection).to receive(:execute).with(anything).and_call_original
end end
it 'returns true when PostgreSQL FDW is enabled' do it 'returns true when PostgreSQL FDW is enabled' do
expect(::Geo::BaseRegistry.connection).to receive(:execute).with(fdw_check).and_return([{ 'count' => 1 }]) expect(::Geo::TrackingBase.connection).to receive(:execute).with(fdw_check).and_return([{ 'count' => 1 }])
expect(described_class.fdw_capable?).to be_truthy
end
it 'returns false when PostgreSQL FDW is not enabled' do
expect(::Geo::TrackingBase.connection).to receive(:execute).with(fdw_check).and_return([{ 'count' => 0 }])
expect(described_class.fdw?).to be_truthy expect(described_class.fdw_capable?).to be_falsey
end end
end
describe 'fdw?' do
it 'returns false when PostgreSQL FDW is not enabled' do it 'returns false when PostgreSQL FDW is not enabled' do
expect(::Geo::BaseRegistry.connection).to receive(:execute).with(fdw_check).and_return([{ 'count' => 0 }]) allow(::Geo::TrackingBase.connection).to receive(:execute).and_return([{ 'count' => 0 }])
allow(Rails.configuration).to receive(:geo_database).and_return('fdw' => true)
expect(described_class.fdw?).to be_falsey expect(described_class.fdw?).to be_falsey
end end
context 'with fdw capable' do
before do
allow(described_class).to receive(:fdw_capable?).and_return(true)
end
it 'returns true by default' do
allow(Rails.configuration).to receive(:geo_database).and_return('fdw' => nil)
expect(described_class.fdw?).to be_truthy
end
it 'returns false if configured in `config/database_geo.yml`' do
allow(Rails.configuration).to receive(:geo_database).and_return('fdw' => false)
expect(described_class.fdw?).to be_falsey
end
it 'returns true if configured in `config/database_geo.yml`' do
allow(Rails.configuration).to receive(:geo_database).and_return('fdw' => true)
expect(described_class.fdw?).to be_truthy
end
end
end end
describe 'primary?' do describe 'primary?' do
......
...@@ -78,6 +78,17 @@ describe GeoNode, type: :model do ...@@ -78,6 +78,17 @@ describe GeoNode, type: :model do
end end
end end
describe '#repair' do
it 'creates an oauth application for a Geo secondary node' do
stub_current_geo_node(node)
node.update_attribute(:oauth_application, nil)
node.repair
expect(node.oauth_application).to be_present
end
end
describe '#current?' do describe '#current?' do
it 'returns true when node is the current node' do it 'returns true when node is the current node' do
node = described_class.new(url: described_class.current_node_url) node = described_class.new(url: described_class.current_node_url)
......
FactoryBot.define do FactoryBot.define do
factory :geo_node_status do factory :geo_node_status do
sequence(:id)
geo_node geo_node
storage_shards { StorageShard.all } storage_shards { StorageShard.all }
......
require 'spec_helper' require 'spec_helper'
describe GeoNodeStatusEntity, :postgresql do describe API::Entities::GeoNodeStatus, :postgresql do
include ::EE::GeoHelpers include ::EE::GeoHelpers
let(:geo_node_status) { build(:geo_node_status) } let(:geo_node_status) { build(:geo_node_status) }
...@@ -11,38 +11,6 @@ describe GeoNodeStatusEntity, :postgresql do ...@@ -11,38 +11,6 @@ describe GeoNodeStatusEntity, :postgresql do
before { stub_primary_node } before { stub_primary_node }
it { is_expected.to have_key(:geo_node_id) }
it { is_expected.to have_key(:healthy) }
it { is_expected.to have_key(:health) }
it { is_expected.to have_key(:attachments_count) }
it { is_expected.to have_key(:attachments_failed_count) }
it { is_expected.to have_key(:attachments_synced_count) }
it { is_expected.to have_key(:attachments_synced_in_percentage) }
it { is_expected.to have_key(:lfs_objects_count) }
it { is_expected.to have_key(:lfs_objects_failed_count) }
it { is_expected.to have_key(:lfs_objects_synced_count) }
it { is_expected.to have_key(:lfs_objects_synced_in_percentage) }
it { is_expected.to have_key(:job_artifacts_count) }
it { is_expected.to have_key(:job_artifacts_failed_count) }
it { is_expected.to have_key(:job_artifacts_synced_count) }
it { is_expected.to have_key(:job_artifacts_synced_in_percentage) }
it { is_expected.to have_key(:repositories_count) }
it { is_expected.to have_key(:repositories_failed_count) }
it { is_expected.to have_key(:repositories_synced_count)}
it { is_expected.to have_key(:repositories_synced_in_percentage) }
it { is_expected.to have_key(:wikis_count) }
it { is_expected.to have_key(:wikis_failed_count) }
it { is_expected.to have_key(:wikis_synced_count)}
it { is_expected.to have_key(:wikis_synced_in_percentage) }
it { is_expected.to have_key(:replication_slots_count) }
it { is_expected.to have_key(:replication_slots_used_count)}
it { is_expected.to have_key(:replication_slots_used_in_percentage) }
it { is_expected.to have_key(:replication_slots_max_retained_wal_bytes) }
it { is_expected.to have_key(:last_successful_status_check_timestamp) }
it { is_expected.to have_key(:namespaces) }
it { is_expected.to have_key(:storage_shards) }
it { is_expected.to have_key(:storage_shards_match) }
describe '#healthy' do describe '#healthy' do
context 'when node is healthy' do context 'when node is healthy' do
it 'returns true' do it 'returns true' do
......
require 'spec_helper' require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170518231126_fix_wrongly_renamed_routes.rb') require Rails.root.join('db', 'post_migrate', '20170518231126_fix_wrongly_renamed_routes.rb')
describe FixWronglyRenamedRoutes, truncate: true do describe FixWronglyRenamedRoutes, :truncate, :migration do
let(:subject) { described_class.new } let(:subject) { described_class.new }
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
let(:routes_table) { table(:routes) }
let(:broken_namespace) do let(:broken_namespace) do
namespace = create(:group, name: 'apiis') namespaces_table.create!(name: 'apiis', path: 'apiis').tap do |namespace|
namespace.route.update_attribute(:path, 'api0is') routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'api0is', path: 'api0is')
namespace end
end end
let(:broken_namespace_route) { routes_table.where(source_type: 'Namespace', source_id: broken_namespace.id).first }
describe '#wrongly_renamed' do describe '#wrongly_renamed' do
it "includes routes that have names that don't match their namespace" do it "includes routes that have names that don't match their namespace" do
broken_namespace broken_namespace
_other_namespace = create(:group, name: 'api0') other_namespace = namespaces_table.create!(name: 'api0', path: 'api0')
routes_table.create!(source_type: 'Namespace', source_id: other_namespace.id, name: 'api0', path: 'api0')
expect(subject.wrongly_renamed.map(&:id)) expect(subject.wrongly_renamed.map(&:id))
.to contain_exactly(broken_namespace.route.id) .to contain_exactly(broken_namespace_route.id)
end end
end end
describe "#paths_and_corrections" do describe "#paths_and_corrections" do
it 'finds the wrong path and gets the correction from the namespace' do it 'finds the wrong path and gets the correction from the namespace' do
broken_namespace broken_namespace
namespace = create(:group, name: 'uploads-test') namespaces_table.create!(name: 'uploads-test', path: 'uploads-test').tap do |namespace|
namespace.route.update_attribute(:path, 'uploads0-test') routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'uploads-test', path: 'uploads0-test')
end
expected_result = [ expected_result = [
{ 'namespace_path' => 'apiis', 'path' => 'api0is' }, { 'namespace_path' => 'apiis', 'path' => 'api0is' },
...@@ -36,38 +42,45 @@ describe FixWronglyRenamedRoutes, truncate: true do ...@@ -36,38 +42,45 @@ describe FixWronglyRenamedRoutes, truncate: true do
describe '#routes_in_namespace_query' do describe '#routes_in_namespace_query' do
it 'includes only the required routes' do it 'includes only the required routes' do
namespace = create(:group, path: 'hello') namespace = namespaces_table.create!(name: 'hello', path: 'hello')
project = create(:project, namespace: namespace) namespace_route = routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'hello', path: 'hello')
_other_namespace = create(:group, path: 'hello0') project = projects_table.new(name: 'my-project', path: 'my-project', namespace_id: namespace.id).tap do |project|
project.save!(validate: false)
result = Route.where(subject.routes_in_namespace_query('hello')) end
routes_table.create!(source_type: 'Project', source_id: project.id, name: 'my-project', path: 'hello/my-project')
expect(result).to contain_exactly(namespace.route, project.route) _other_namespace = namespaces_table.create!(name: 'hello0', path: 'hello0')
result = routes_table.where(subject.routes_in_namespace_query('hello'))
project_route = routes_table.where(source_type: 'Project', source_id: project.id).first
expect(result).to contain_exactly(namespace_route, project_route)
end end
end end
describe '#up' do describe '#up' do
let(:broken_project) do
project = create(:project, namespace: broken_namespace, path: 'broken-project')
project.route.update_attribute(:path, 'api0is/broken-project')
project
end
it 'renames incorrectly named routes' do it 'renames incorrectly named routes' do
broken_project broken_project =
projects_table.new(name: 'broken-project', path: 'broken-project', namespace_id: broken_namespace.id).tap do |project|
project.save!(validate: false)
routes_table.create!(source_type: 'Project', source_id: project.id, name: 'broken-project', path: 'api0is/broken-project')
end
subject.up subject.up
expect(broken_project.route.reload.path).to eq('apiis/broken-project') broken_project_route = routes_table.where(source_type: 'Project', source_id: broken_project.id).first
expect(broken_namespace.route.reload.path).to eq('apiis')
expect(broken_project_route.path).to eq('apiis/broken-project')
expect(broken_namespace_route.reload.path).to eq('apiis')
end end
it "doesn't touch namespaces that look like something that should be renamed" do it "doesn't touch namespaces that look like something that should be renamed" do
namespace = create(:group, path: 'api0') namespaces_table.create!(name: 'apiis', path: 'apiis')
namespace = namespaces_table.create!(name: 'hello', path: 'api0')
namespace_route = routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'hello', path: 'api0')
subject.up subject.up
expect(namespace.route.reload.path).to eq('api0') expect(namespace_route.reload.path).to eq('api0')
end end
end end
end end
...@@ -6,20 +6,19 @@ describe API::GeoNodes, :geo, api: true do ...@@ -6,20 +6,19 @@ describe API::GeoNodes, :geo, api: true do
set(:primary) { create(:geo_node, :primary) } set(:primary) { create(:geo_node, :primary) }
set(:secondary) { create(:geo_node) } set(:secondary) { create(:geo_node) }
set(:another_secondary) { create(:geo_node) } set(:secondary_status) { create(:geo_node_status, :healthy, geo_node: secondary) }
set(:secondary_status) { create(:geo_node_status, :healthy, geo_node_id: secondary.id) } let(:unexisting_node_id) { GeoNode.maximum(:id).to_i.succ }
set(:another_secondary_status) { create(:geo_node_status, :healthy, geo_node_id: another_secondary.id) }
let(:admin) { create(:admin) } set(:admin) { create(:admin) }
let(:user) { create(:user) } set(:user) { create(:user) }
describe 'GET /geo_nodes' do describe 'GET /geo_nodes' do
it 'retrieves the Geo nodes if admin is logged in' do it 'retrieves the Geo nodes if admin is logged in' do
get api("/geo_nodes", admin) get api("/geo_nodes", admin)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('geo_nodes') expect(response).to match_response_schema('public_api/v4/geo_nodes', dir: 'ee')
end end
it 'denies access if not admin' do it 'denies access if not admin' do
...@@ -34,7 +33,15 @@ describe API::GeoNodes, :geo, api: true do ...@@ -34,7 +33,15 @@ describe API::GeoNodes, :geo, api: true do
get api("/geo_nodes/#{primary.id}", admin) get api("/geo_nodes/#{primary.id}", admin)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('geo_node') expect(response).to match_response_schema('public_api/v4/geo_node', dir: 'ee')
links = json_response['_links']
expect(links['self']).to end_with("/api/v4/geo_nodes/#{primary.id}")
expect(links['repair']).to end_with("/api/v4/geo_nodes/#{primary.id}/repair")
end
it_behaves_like '404 response' do
let(:request) { get api("/geo_nodes/#{unexisting_node_id}", admin) }
end end
it 'denies access if not admin' do it 'denies access if not admin' do
...@@ -49,7 +56,7 @@ describe API::GeoNodes, :geo, api: true do ...@@ -49,7 +56,7 @@ describe API::GeoNodes, :geo, api: true do
get api("/geo_nodes/status", admin) get api("/geo_nodes/status", admin)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('geo_node_statuses') expect(response).to match_response_schema('public_api/v4/geo_node_statuses', dir: 'ee')
end end
it 'denies access if not admin' do it 'denies access if not admin' do
...@@ -68,7 +75,11 @@ describe API::GeoNodes, :geo, api: true do ...@@ -68,7 +75,11 @@ describe API::GeoNodes, :geo, api: true do
get api("/geo_nodes/#{secondary.id}/status", admin) get api("/geo_nodes/#{secondary.id}/status", admin)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('geo_node_status') expect(response).to match_response_schema('public_api/v4/geo_node_status', dir: 'ee')
links = json_response['_links']
expect(links['self']).to end_with("/api/v4/geo_nodes/#{secondary.id}/status")
expect(links['node']).to end_with("/api/v4/geo_nodes/#{secondary.id}")
end end
it 'fetches the current node status' do it 'fetches the current node status' do
...@@ -80,14 +91,82 @@ describe API::GeoNodes, :geo, api: true do ...@@ -80,14 +91,82 @@ describe API::GeoNodes, :geo, api: true do
get api("/geo_nodes/#{secondary.id}/status", admin) get api("/geo_nodes/#{secondary.id}/status", admin)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('geo_node_status') expect(response).to match_response_schema('public_api/v4/geo_node_status', dir: 'ee')
end
it_behaves_like '404 response' do
let(:request) { get api("/geo_nodes/#{unexisting_node_id}/status", admin) }
end end
it 'denies access if not admin' do it 'denies access if not admin' do
get api('/geo_nodes', user) get api("/geo_nodes/#{secondary.id}/status", user)
expect(response).to have_gitlab_http_status(403)
end
end
describe 'POST /geo_nodes/:id/repair' do
it_behaves_like '404 response' do
let(:request) { post api("/geo_nodes/#{unexisting_node_id}/status", admin) }
end
it 'denies access if not admin' do
post api("/geo_nodes/#{secondary.id}/repair", user)
expect(response).to have_gitlab_http_status(403)
end
it 'returns 200 for the primary node' do
post api("/geo_nodes/#{primary.id}/repair", admin)
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/geo_node_status', dir: 'ee')
end
it 'returns 200 when node does not need repairing' do
allow_any_instance_of(GeoNode).to receive(:missing_oauth_application?).and_return(false)
post api("/geo_nodes/#{secondary.id}/repair", admin)
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/geo_node_status', dir: 'ee')
end
it 'repairs a secondary with oauth application missing' do
allow_any_instance_of(GeoNode).to receive(:missing_oauth_application?).and_return(true)
post api("/geo_nodes/#{secondary.id}/repair", admin)
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/geo_node_status', dir: 'ee')
end
end
describe 'PUT /geo_nodes/:id' do
it_behaves_like '404 response' do
let(:request) { get api("/geo_nodes/#{unexisting_node_id}/status", admin) }
end
it 'denies access if not admin' do
put api("/geo_nodes/#{secondary.id}", user), {}
expect(response).to have_gitlab_http_status(403) expect(response).to have_gitlab_http_status(403)
end end
it 'updates the parameters' do
params = {
enabled: false,
url: 'https://updated.example.com/',
files_max_capacity: 33,
repos_max_capacity: 44
}.stringify_keys
put api("/geo_nodes/#{secondary.id}", admin), params
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/geo_node', dir: 'ee')
expect(json_response).to include(params)
end
end end
describe 'GET /geo_nodes/current/failures/:type' do describe 'GET /geo_nodes/current/failures/:type' do
...@@ -101,7 +180,7 @@ describe API::GeoNodes, :geo, api: true do ...@@ -101,7 +180,7 @@ describe API::GeoNodes, :geo, api: true do
get api("/geo_nodes/current/failures", admin) get api("/geo_nodes/current/failures", admin)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('geo_project_registry') expect(response).to match_response_schema('public_api/v4/geo_project_registry', dir: 'ee')
end end
it 'does not show any registry when there is no failure' do it 'does not show any registry when there is no failure' do
......
...@@ -185,7 +185,7 @@ describe API::Geo do ...@@ -185,7 +185,7 @@ describe API::Geo do
get api('/geo/status'), nil, request.headers get api('/geo/status'), nil, request.headers
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('geo_node_status') expect(response).to match_response_schema('public_api/v4/geo_node_status', dir: 'ee')
end end
end end
...@@ -199,7 +199,7 @@ describe API::Geo do ...@@ -199,7 +199,7 @@ describe API::Geo do
get api('/geo/status'), nil, request.headers get api('/geo/status'), nil, request.headers
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('geo_node_status') expect(response).to match_response_schema('public_api/v4/geo_node_status', dir: 'ee')
end end
end end
end end
......
...@@ -29,9 +29,12 @@ describe API::Jobs do ...@@ -29,9 +29,12 @@ describe API::Jobs do
describe 'GET /projects/:id/jobs' do describe 'GET /projects/:id/jobs' do
let(:query) { Hash.new } let(:query) { Hash.new }
before do before do |example|
job job
get api("/projects/#{project.id}/jobs", api_user), query
unless example.metadata[:skip_before_request]
get api("/projects/#{project.id}/jobs", api_user), query
end
end end
context 'authorized user' do context 'authorized user' do
...@@ -56,6 +59,23 @@ describe API::Jobs do ...@@ -56,6 +59,23 @@ describe API::Jobs do
expect(json_job['pipeline']['status']).to eq job.pipeline.status expect(json_job['pipeline']['status']).to eq job.pipeline.status
end end
it 'avoids N+1 queries', skip_before_request: true do
first_build = create(:ci_build, :artifacts, pipeline: pipeline)
first_build.runner = create(:ci_runner)
first_build.user = create(:user)
first_build.save
control_count = ActiveRecord::QueryRecorder.new { go }.count
second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch)
second_build = create(:ci_build, :artifacts, pipeline: second_pipeline)
second_build.runner = create(:ci_runner)
second_build.user = create(:user)
second_build.save
expect { go }.not_to exceed_query_limit(control_count)
end
context 'filter project with one scope element' do context 'filter project with one scope element' do
let(:query) { { 'scope' => 'pending' } } let(:query) { { 'scope' => 'pending' } }
...@@ -88,6 +108,10 @@ describe API::Jobs do ...@@ -88,6 +108,10 @@ describe API::Jobs do
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(401)
end end
end end
def go
get api("/projects/#{project.id}/jobs", api_user), query
end
end end
describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do
......
...@@ -13,11 +13,13 @@ describe API::V3::Builds do ...@@ -13,11 +13,13 @@ describe API::V3::Builds do
describe 'GET /projects/:id/builds ' do describe 'GET /projects/:id/builds ' do
let(:query) { '' } let(:query) { '' }
before do before do |example|
build build
create(:ci_build, :skipped, pipeline: pipeline) create(:ci_build, :skipped, pipeline: pipeline)
get v3_api("/projects/#{project.id}/builds?#{query}", api_user) unless example.metadata[:skip_before_request]
get v3_api("/projects/#{project.id}/builds?#{query}", api_user)
end
end end
context 'authorized user' do context 'authorized user' do
...@@ -41,6 +43,23 @@ describe API::V3::Builds do ...@@ -41,6 +43,23 @@ describe API::V3::Builds do
expect(json_build['pipeline']['status']).to eq build.pipeline.status expect(json_build['pipeline']['status']).to eq build.pipeline.status
end end
it 'avoids N+1 queries', skip_before_request: true do
first_build = create(:ci_build, :artifacts, pipeline: pipeline)
first_build.runner = create(:ci_runner)
first_build.user = create(:user)
first_build.save
control_count = ActiveRecord::QueryRecorder.new { go }.count
second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch)
second_build = create(:ci_build, :artifacts, pipeline: second_pipeline)
second_build.runner = create(:ci_runner)
second_build.user = create(:user)
second_build.save
expect { go }.not_to exceed_query_limit(control_count)
end
context 'filter project with one scope element' do context 'filter project with one scope element' do
let(:query) { 'scope=pending' } let(:query) { 'scope=pending' }
...@@ -85,6 +104,10 @@ describe API::V3::Builds do ...@@ -85,6 +104,10 @@ describe API::V3::Builds do
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(401)
end end
end end
def go
get v3_api("/projects/#{project.id}/builds?#{query}", api_user)
end
end end
describe 'GET /projects/:id/repository/commits/:sha/builds' do describe 'GET /projects/:id/repository/commits/:sha/builds' do
......
require 'rake_helper' require 'rake_helper'
describe 'geo rake tasks' do describe 'geo rake tasks' do
include ::EE::GeoHelpers
before do before do
Rake.application.rake_require 'tasks/geo' Rake.application.rake_require 'tasks/geo'
stub_licensed_features(geo: true)
end end
describe 'set_primary_node task' do describe 'set_primary_node task' do
before do before do
expect(Gitlab::Geo).to receive(:license_allows?).and_return(true)
stub_config_setting(protocol: 'https') stub_config_setting(protocol: 'https')
end end
...@@ -26,14 +28,10 @@ describe 'geo rake tasks' do ...@@ -26,14 +28,10 @@ describe 'geo rake tasks' do
end end
describe 'set_secondary_as_primary task' do describe 'set_secondary_as_primary task' do
include ::EE::GeoHelpers
let!(:current_node) { create(:geo_node) } let!(:current_node) { create(:geo_node) }
let!(:primary_node) { create(:geo_node, :primary) } let!(:primary_node) { create(:geo_node, :primary) }
before do before do
expect(Gitlab::Geo).to receive(:license_allows?).and_return(true)
stub_current_geo_node(current_node) stub_current_geo_node(current_node)
end end
...@@ -44,4 +42,19 @@ describe 'geo rake tasks' do ...@@ -44,4 +42,19 @@ describe 'geo rake tasks' do
expect(GeoNode.count).to eq(1) expect(GeoNode.count).to eq(1)
end end
end end
describe 'update_primary_node_url task' do
let(:primary_node) { create(:geo_node, :primary, url: 'https://secondary.geo.example.com') }
before do
allow(GeoNode).to receive(:current_node_url).and_return('https://primary.geo.example.com')
stub_current_geo_node(primary_node)
end
it 'updates Geo primary node URL' do
run_rake_task('geo:update_primary_node_url')
expect(primary_node.reload.url).to eq 'https://primary.geo.example.com/'
end
end
end end
...@@ -5175,10 +5175,6 @@ preserve@^0.2.0: ...@@ -5175,10 +5175,6 @@ preserve@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@1.9.2:
version "1.9.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827"
prettier@^1.7.0: prettier@^1.7.0:
version "1.8.2" version "1.8.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.8.2.tgz#bff83e7fd573933c607875e5ba3abbdffb96aeb8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.8.2.tgz#bff83e7fd573933c607875e5ba3abbdffb96aeb8"
......
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