Commit 6f471c29 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 8b778fae 36b3ca5c
--- ---
name: debian_packages name: debian_packages
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42670 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42670
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/5835 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337288
milestone: '13.5' milestone: '13.5'
type: development type: development
group: group::package group: group::package
......
...@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/333034 ...@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/333034
milestone: '14.0' milestone: '14.0'
type: development type: development
group: group::gitaly group: group::gitaly
default_enabled: false default_enabled: true
...@@ -568,12 +568,12 @@ in the second step, do not supply the `EXTERNAL_URL` value. ...@@ -568,12 +568,12 @@ in the second step, do not supply the `EXTERNAL_URL` value.
# Sets `max_replication_slots` to double the number of database nodes. # Sets `max_replication_slots` to double the number of database nodes.
# Patroni uses one extra slot per node when initiating the replication. # Patroni uses one extra slot per node when initiating the replication.
patroni['postgresql']['max_replication_slots'] = 8 patroni['postgresql']['max_replication_slots'] = 6
# Set `max_wal_senders` to one more than the number of replication slots in the cluster. # Set `max_wal_senders` to one more than the number of replication slots in the cluster.
# This is used to prevent replication from using up all of the # This is used to prevent replication from using up all of the
# available database connections. # available database connections.
patroni['postgresql']['max_wal_senders'] = 9 patroni['postgresql']['max_wal_senders'] = 7
# Incoming recommended value for max connections is 500. See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5691. # Incoming recommended value for max connections is 500. See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5691.
patroni['postgresql']['max_connections'] = 500 patroni['postgresql']['max_connections'] = 500
......
...@@ -570,12 +570,12 @@ in the second step, do not supply the `EXTERNAL_URL` value. ...@@ -570,12 +570,12 @@ in the second step, do not supply the `EXTERNAL_URL` value.
# Sets `max_replication_slots` to double the number of database nodes. # Sets `max_replication_slots` to double the number of database nodes.
# Patroni uses one extra slot per node when initiating the replication. # Patroni uses one extra slot per node when initiating the replication.
patroni['postgresql']['max_replication_slots'] = 8 patroni['postgresql']['max_replication_slots'] = 6
# Set `max_wal_senders` to one more than the number of replication slots in the cluster. # Set `max_wal_senders` to one more than the number of replication slots in the cluster.
# This is used to prevent replication from using up all of the # This is used to prevent replication from using up all of the
# available database connections. # available database connections.
patroni['postgresql']['max_wal_senders'] = 9 patroni['postgresql']['max_wal_senders'] = 7
# Incoming recommended value for max connections is 500. See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5691. # Incoming recommended value for max connections is 500. See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5691.
patroni['postgresql']['max_connections'] = 500 patroni['postgresql']['max_connections'] = 500
......
...@@ -577,12 +577,12 @@ in the second step, do not supply the `EXTERNAL_URL` value. ...@@ -577,12 +577,12 @@ in the second step, do not supply the `EXTERNAL_URL` value.
# Sets `max_replication_slots` to double the number of database nodes. # Sets `max_replication_slots` to double the number of database nodes.
# Patroni uses one extra slot per node when initiating the replication. # Patroni uses one extra slot per node when initiating the replication.
patroni['postgresql']['max_replication_slots'] = 8 patroni['postgresql']['max_replication_slots'] = 6
# Set `max_wal_senders` to one more than the number of replication slots in the cluster. # Set `max_wal_senders` to one more than the number of replication slots in the cluster.
# This is used to prevent replication from using up all of the # This is used to prevent replication from using up all of the
# available database connections. # available database connections.
patroni['postgresql']['max_wal_senders'] = 9 patroni['postgresql']['max_wal_senders'] = 7
# Incoming recommended value for max connections is 500. See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5691. # Incoming recommended value for max connections is 500. See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5691.
patroni['postgresql']['max_connections'] = 500 patroni['postgresql']['max_connections'] = 500
......
...@@ -841,12 +841,12 @@ in the second step, do not supply the `EXTERNAL_URL` value. ...@@ -841,12 +841,12 @@ in the second step, do not supply the `EXTERNAL_URL` value.
# Sets `max_replication_slots` to double the number of database nodes. # Sets `max_replication_slots` to double the number of database nodes.
# Patroni uses one extra slot per node when initiating the replication. # Patroni uses one extra slot per node when initiating the replication.
patroni['postgresql']['max_replication_slots'] = 8 patroni['postgresql']['max_replication_slots'] = 6
# Set `max_wal_senders` to one more than the number of replication slots in the cluster. # Set `max_wal_senders` to one more than the number of replication slots in the cluster.
# This is used to prevent replication from using up all of the # This is used to prevent replication from using up all of the
# available database connections. # available database connections.
patroni['postgresql']['max_wal_senders'] = 9 patroni['postgresql']['max_wal_senders'] = 7
# Incoming recommended value for max connections is 500. See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5691. # Incoming recommended value for max connections is 500. See https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5691.
patroni['postgresql']['max_connections'] = 500 patroni['postgresql']['max_connections'] = 500
......
...@@ -259,3 +259,27 @@ job1: ...@@ -259,3 +259,27 @@ job1:
- echo 'this line should be hidden automatically after loading the job log' - echo 'this line should be hidden automatically after loading the job log'
- echo -e "\e[0Ksection_end:`date +%s`:my_first_section\r\e[0K" - echo -e "\e[0Ksection_end:`date +%s`:my_first_section\r\e[0K"
``` ```
## Deployment jobs
Deployment jobs are a specific kind of CI job in that they deploy code to
[environments](../environments/index.md). A deployment job is any job that
uses the `environment` keyword and the [`start` environment `action`](../yaml/index.md#environmentaction).
Deployment jobs do not need to be in the `deploy` stage. The following `deploy me`
job is an example of a deployment job. `action: start` is the default behavior and
is defined for the sake of the example, but you can omit it:
```yaml
deploy me:
script:
- deploy-to-cats.sh
environment:
name: production
url: https://cats.example.com
action: start
```
The behavior of deployment jobs can be controlled with
[deployment safety](../environments/deployment_safety.md) settings like
[skipping outdated deployment jobs](../environments/deployment_safety.md#prevent-deployments-during-deploy-freeze-windows)
and [ensuring only one deployment job runs at a time](../environments/deployment_safety.md#ensure-only-one-deployment-job-runs-at-a-time).
...@@ -1472,3 +1472,47 @@ If this happens, examine the following: ...@@ -1472,3 +1472,47 @@ If this happens, examine the following:
- Confirm there is sufficient disk space for the Gzip operation. - Confirm there is sufficient disk space for the Gzip operation.
- If NFS is being used, check if the mount option `timeout` is set. The - If NFS is being used, check if the mount option `timeout` is set. The
default is `600`, and changing this to smaller values results in this error. default is `600`, and changing this to smaller values results in this error.
### `gitaly-backup` for repository backup and restore **(FREE SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/333034) in GitLab 14.2.
> - [Deployed behind a feature flag](../user/feature_flags.md), enabled by default.
> - Recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-or-enable-gitaly-backup).
There can be
[risks when disabling released features](../user/feature_flags.md#risks-when-disabling-released-features).
Refer to this feature's version history for more details.
`gitaly-backup` is used by the backup Rake task to create and restore repository backups from Gitaly.
`gitaly-backup` replaces the previous backup method that directly calls RPCs on Gitaly from GitLab.
The backup Rake task must be able to find this executable. It can be configured in Omnibus GitLab packages:
1. Add the following to `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['backup_gitaly_backup_path'] = '/path/to/gitaly-backup'
```
1. [Reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure)
for the changes to take effect
#### Disable or enable `gitaly-backup`
`gitaly-backup` is under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md)
can opt to disable it.
To disable it:
```ruby
Feature.disable(:gitaly_backup)
```
To enable it:
```ruby
Feature.enable(:gitaly_backup)
```
...@@ -102,7 +102,7 @@ release tag. When the `released_at` date and time has passed, the badge is autom ...@@ -102,7 +102,7 @@ release tag. When the `released_at` date and time has passed, the badge is autom
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26016) in GitLab 12.6. Asset link editing was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9427) in GitLab 12.10. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26016) in GitLab 12.6. Asset link editing was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9427) in GitLab 12.10.
Only users with at least the Developer can edit releases. Only users with at least the Developer role can edit releases.
Read more about [Release permissions](#release-permissions). Read more about [Release permissions](#release-permissions).
To edit the details of a release: To edit the details of a release:
......
...@@ -96,7 +96,16 @@ export default { ...@@ -96,7 +96,16 @@ export default {
}, },
}, },
methods: { methods: {
triggerFormValidation() {
this.validateForm('timezone');
},
createSchedule() { createSchedule() {
this.triggerFormValidation();
if (!this.isFormValid) {
return;
}
this.loading = true; this.loading = true;
const { projectPath } = this; const { projectPath } = this;
...@@ -127,6 +136,7 @@ export default { ...@@ -127,6 +136,7 @@ export default {
if (error) { if (error) {
throw error; throw error;
} }
this.$refs.addUpdateScheduleModal.hide(); this.$refs.addUpdateScheduleModal.hide();
this.$emit('scheduleCreated'); this.$emit('scheduleCreated');
this.clearScheduleForm(); this.clearScheduleForm();
...@@ -140,11 +150,12 @@ export default { ...@@ -140,11 +150,12 @@ export default {
}); });
}, },
editSchedule() { editSchedule() {
this.loading = true;
const { const {
projectPath, projectPath,
form: { timezone }, form: { timezone },
} = this; } = this;
this.loading = true;
this.$apollo this.$apollo
.mutate({ .mutate({
......
import { GlModal, GlAlert } from '@gitlab/ui'; import { GlModal, GlAlert } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import AddEditScheduleForm from 'ee/oncall_schedules/components/add_edit_schedule_form.vue'; import AddEditScheduleForm from 'ee/oncall_schedules/components/add_edit_schedule_form.vue';
import AddEditScheduleModal, { import AddEditScheduleModal, {
...@@ -58,9 +59,9 @@ describe('AddScheduleModal', () => { ...@@ -58,9 +59,9 @@ describe('AddScheduleModal', () => {
}; };
async function awaitApolloDomMock() { async function awaitApolloDomMock() {
await wrapper.vm.$nextTick(); // kick off the DOM update await nextTick(); // kick off the DOM update
await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises) await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises)
await wrapper.vm.$nextTick(); // kick off the DOM update for flash await nextTick(); // kick off the DOM update for flash
} }
async function updateSchedule(localWrapper) { async function updateSchedule(localWrapper) {
...@@ -119,6 +120,8 @@ describe('AddScheduleModal', () => { ...@@ -119,6 +120,8 @@ describe('AddScheduleModal', () => {
const findAlert = () => wrapper.findComponent(GlAlert); const findAlert = () => wrapper.findComponent(GlAlert);
const findModalForm = () => wrapper.findComponent(AddEditScheduleForm); const findModalForm = () => wrapper.findComponent(AddEditScheduleForm);
const submitForm = () => findModal().vm.$emit('primary', { preventDefault: jest.fn() });
describe('Schedule create', () => { describe('Schedule create', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ modalId: addScheduleModalId }); createComponent({ modalId: addScheduleModalId });
...@@ -134,9 +137,32 @@ describe('AddScheduleModal', () => { ...@@ -134,9 +137,32 @@ describe('AddScheduleModal', () => {
}); });
}); });
it('makes a request with form data to create a schedule', () => { it('prevents form submit if schedule is invalid', () => {
mutate.mockResolvedValueOnce({}); createComponent({
findModal().vm.$emit('primary', { preventDefault: jest.fn() }); modalId: addScheduleModalId,
data: { form: { name: 'schedule', timezone: null } },
});
submitForm();
expect(mutate).not.toHaveBeenCalled();
});
it("doesn't hide a modal and shows error alert on fail", async () => {
const error = 'some error';
mutate.mockImplementation(() => Promise.reject(error));
submitForm();
await waitForPromises();
const alert = findAlert();
expect(mockHideModal).not.toHaveBeenCalled();
expect(alert.exists()).toBe(true);
expect(alert.text()).toContain(error);
});
it('makes a request with form data to create a schedule and hides a modal', async () => {
mutate.mockImplementation(() =>
Promise.resolve({ data: { oncallScheduleCreate: { errors: [] } } }),
);
submitForm();
expect(mutate).toHaveBeenCalledWith({ expect(mutate).toHaveBeenCalledWith({
mutation: expect.any(Object), mutation: expect.any(Object),
update: expect.any(Function), update: expect.any(Function),
...@@ -148,29 +174,15 @@ describe('AddScheduleModal', () => { ...@@ -148,29 +174,15 @@ describe('AddScheduleModal', () => {
}, },
}, },
}); });
});
it('hides the modal on successful schedule creation', async () => {
mutate.mockResolvedValueOnce({ data: { oncallScheduleCreate: { errors: [] } } });
findModal().vm.$emit('primary', { preventDefault: jest.fn() });
await waitForPromises(); await waitForPromises();
expect(mockHideModal).toHaveBeenCalled(); expect(mockHideModal).toHaveBeenCalled();
}); });
it("doesn't hide a modal and shows error alert on fail", async () => {
const error = 'some error';
mutate.mockResolvedValueOnce({ data: { oncallScheduleCreate: { errors: [error] } } });
findModal().vm.$emit('primary', { preventDefault: jest.fn() });
await waitForPromises();
const alert = findAlert();
expect(mockHideModal).not.toHaveBeenCalled();
expect(alert.exists()).toBe(true);
expect(alert.text()).toContain(error);
});
it('should clear the schedule form on a successful creation', () => { it('should clear the schedule form on a successful creation', () => {
mutate.mockResolvedValueOnce({}); mutate.mockImplementation(() =>
findModal().vm.$emit('primary', { preventDefault: jest.fn() }); Promise.resolve({ data: { oncallScheduleCreate: { errors: [] } } }),
);
submitForm();
expect(findModalForm().props('form')).toMatchObject({ expect(findModalForm().props('form')).toMatchObject({
name: '', name: '',
description: '', description: '',
...@@ -194,37 +206,31 @@ describe('AddScheduleModal', () => { ...@@ -194,37 +206,31 @@ describe('AddScheduleModal', () => {
}); });
}); });
describe('Schedule update apollo API call', () => { it("doesn't hide the modal on fail", async () => {
it('makes a request with `oncallScheduleUpdate` to update a schedule', () => { const error = 'some error';
mutate.mockResolvedValueOnce({}); mutate.mockRejectedValueOnce(error);
findModal().vm.$emit('primary', { preventDefault: jest.fn() }); submitForm();
expect(mutate).toHaveBeenCalledWith({ await waitForPromises();
mutation: expect.any(Object), expect(mockHideModal).not.toHaveBeenCalled();
update: expect.anything(), });
variables: {
iid: mockSchedule.iid,
projectPath,
name: mockSchedule.name,
description: mockSchedule.description,
timezone: mockSchedule.timezone.identifier,
},
});
});
it('hides the modal on successful schedule creation', async () => { it('makes a request with `oncallScheduleUpdate` to update a schedule and hides a modal on successful update', async () => {
mutate.mockResolvedValueOnce({ data: { oncallScheduleUpdate: { errors: [] } } }); mutate.mockResolvedValueOnce({ data: { oncallScheduleUpdate: { errors: [] } } });
findModal().vm.$emit('primary', { preventDefault: jest.fn() }); submitForm();
await waitForPromises();
expect(mockHideModal).toHaveBeenCalled();
});
it("doesn't hide the modal on fail", async () => { expect(mutate).toHaveBeenCalledWith({
const error = 'some error'; mutation: expect.any(Object),
mutate.mockResolvedValueOnce({ data: { oncallScheduleUpdate: { errors: [error] } } }); update: expect.anything(),
findModal().vm.$emit('primary', { preventDefault: jest.fn() }); variables: {
await waitForPromises(); iid: mockSchedule.iid,
expect(mockHideModal).not.toHaveBeenCalled(); projectPath,
name: mockSchedule.name,
description: mockSchedule.description,
timezone: mockSchedule.timezone.identifier,
},
}); });
await waitForPromises();
expect(mockHideModal).toHaveBeenCalled();
}); });
describe('with mocked Apollo client', () => { describe('with mocked Apollo client', () => {
...@@ -255,7 +261,7 @@ describe('AddScheduleModal', () => { ...@@ -255,7 +261,7 @@ describe('AddScheduleModal', () => {
it('it should not reload the page if the timezone has not changed', async () => { it('it should not reload the page if the timezone has not changed', async () => {
mutate.mockResolvedValueOnce({}); mutate.mockResolvedValueOnce({});
findModal().vm.$emit('primary', { preventDefault: jest.fn() }); submitForm();
await waitForPromises(); await waitForPromises();
expect(window.location.reload).not.toHaveBeenCalled(); expect(window.location.reload).not.toHaveBeenCalled();
}); });
...@@ -268,7 +274,7 @@ describe('AddScheduleModal', () => { ...@@ -268,7 +274,7 @@ describe('AddScheduleModal', () => {
modalId: editScheduleModalId, modalId: editScheduleModalId,
}); });
mutate.mockResolvedValueOnce({}); mutate.mockResolvedValueOnce({});
findModal().vm.$emit('primary', { preventDefault: jest.fn() }); submitForm();
expect(mutate).toHaveBeenCalledWith({ expect(mutate).toHaveBeenCalledWith({
mutation: updateOncallScheduleMutation, mutation: updateOncallScheduleMutation,
update: expect.anything(), update: expect.anything(),
......
...@@ -297,7 +297,7 @@ namespace :gitlab do ...@@ -297,7 +297,7 @@ namespace :gitlab do
end end
def repository_backup_strategy def repository_backup_strategy
if Feature.enabled?(:gitaly_backup) if Feature.enabled?(:gitaly_backup, default_enabled: :yaml)
max_concurrency = ENV['GITLAB_BACKUP_MAX_CONCURRENCY'].presence max_concurrency = ENV['GITLAB_BACKUP_MAX_CONCURRENCY'].presence
max_storage_concurrency = ENV['GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY'].presence max_storage_concurrency = ENV['GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY'].presence
Backup::GitalyBackup.new(progress, parallel: max_concurrency, parallel_storage: max_storage_concurrency) Backup::GitalyBackup.new(progress, parallel: max_concurrency, parallel_storage: max_storage_concurrency)
......
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