Commit b478c5e6 authored by Valery Sizov's avatar Valery Sizov

[ci skip]Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee into ce_upstream

parents 0a1fb9ce 1c317af4
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, quotes, prefer-template, no-var, one-var, no-unused-vars, one-var-declaration-per-line, no-void, consistent-return, no-empty, max-len */ /* eslint-disable no-param-reassign, prefer-template, no-var, no-void, consistent-return */
import AccessorUtilities from './lib/utils/accessor'; import AccessorUtilities from './lib/utils/accessor';
window.Autosave = (function() { export default class Autosave {
function Autosave(field, key, resource) { constructor(field, key, resource) {
this.field = field; this.field = field;
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.resource = resource; this.resource = resource;
...@@ -12,14 +13,10 @@ window.Autosave = (function() { ...@@ -12,14 +13,10 @@ window.Autosave = (function() {
this.key = 'autosave/' + key; this.key = 'autosave/' + key;
this.field.data('autosave', this); this.field.data('autosave', this);
this.restore(); this.restore();
this.field.on('input', (function(_this) { this.field.on('input', () => this.save());
return function() {
return _this.save();
};
})(this));
} }
Autosave.prototype.restore = function() { restore() {
var text; var text;
if (!this.isLocalStorageAvailable) return; if (!this.isLocalStorageAvailable) return;
...@@ -40,9 +37,9 @@ window.Autosave = (function() { ...@@ -40,9 +37,9 @@ window.Autosave = (function() {
field.dispatchEvent(event); field.dispatchEvent(event);
} }
} }
}; }
Autosave.prototype.save = function() { save() {
var text; var text;
text = this.field.val(); text = this.field.val();
...@@ -51,15 +48,11 @@ window.Autosave = (function() { ...@@ -51,15 +48,11 @@ window.Autosave = (function() {
} }
return this.reset(); return this.reset();
}; }
Autosave.prototype.reset = function() { reset() {
if (!this.isLocalStorageAvailable) return; if (!this.isLocalStorageAvailable) return;
return window.localStorage.removeItem(this.key); return window.localStorage.removeItem(this.key);
}; }
}
return Autosave;
})();
export default window.Autosave;
...@@ -2,7 +2,7 @@ import Cookies from 'js-cookie'; ...@@ -2,7 +2,7 @@ import Cookies from 'js-cookie';
import _ from 'underscore'; import _ from 'underscore';
import bp from './breakpoints'; import bp from './breakpoints';
export default class NewNavSidebar { export default class ContextualSidebar {
constructor() { constructor() {
this.initDomElements(); this.initDomElements();
this.render(); this.render();
...@@ -55,7 +55,7 @@ export default class NewNavSidebar { ...@@ -55,7 +55,7 @@ export default class NewNavSidebar {
this.$sidebar.toggleClass('sidebar-icons-only', collapsed); this.$sidebar.toggleClass('sidebar-icons-only', collapsed);
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed); this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
} }
NewNavSidebar.setCollapsedCookie(collapsed); ContextualSidebar.setCollapsedCookie(collapsed);
this.toggleSidebarOverflow(); this.toggleSidebarOverflow();
} }
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */
/* global GitLab */ /* global GitLab */
/* global Autosave */
/* global GroupsSelect */ /* global GroupsSelect */
import Pikaday from 'pikaday'; import Pikaday from 'pikaday';
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';
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */
import _ from 'underscore'; import _ from 'underscore';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import NewNavSidebar from './new_sidebar'; import ContextualSidebar from './contextual_sidebar';
import initFlyOutNav from './fly_out_nav'; import initFlyOutNav from './fly_out_nav';
(function() { (function() {
...@@ -51,8 +51,8 @@ import initFlyOutNav from './fly_out_nav'; ...@@ -51,8 +51,8 @@ import initFlyOutNav from './fly_out_nav';
}); });
$(() => { $(() => {
const newNavSidebar = new NewNavSidebar(); const contextualSidebar = new ContextualSidebar();
newNavSidebar.bindEvents(); contextualSidebar.bindEvents();
initFlyOutNav(); initFlyOutNav();
}); });
......
...@@ -41,7 +41,6 @@ import './behaviors/'; ...@@ -41,7 +41,6 @@ import './behaviors/';
import './activities'; import './activities';
import './admin'; import './admin';
import './aside'; import './aside';
import './autosave';
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
import bp from './breakpoints'; import bp from './breakpoints';
import './commits'; import './commits';
......
...@@ -5,7 +5,7 @@ default-case, prefer-template, consistent-return, no-alert, no-return-assign, ...@@ -5,7 +5,7 @@ default-case, prefer-template, consistent-return, no-alert, no-return-assign,
no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new,
brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow,
newline-per-chained-call, no-useless-escape, class-methods-use-this */ newline-per-chained-call, no-useless-escape, class-methods-use-this */
/* global Autosave */
/* global ResolveService */ /* global ResolveService */
/* global mrRefreshWidgetUrl */ /* global mrRefreshWidgetUrl */
...@@ -20,7 +20,12 @@ import Flash from './flash'; ...@@ -20,7 +20,12 @@ import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle'; import CommentTypeToggle from './comment_type_toggle';
import GLForm from './gl_form'; import GLForm from './gl_form';
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
<<<<<<< HEAD
import './autosave'; import './autosave';
=======
import Autosave from './autosave';
import './dropzone_input';
>>>>>>> 1c317af47772ed2862a9c1ed22ab952bb1a84874
import TaskList from './task_list'; import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils'; import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index'; import imageDiffHelper from './image_diff/helpers/index';
......
<script> <script>
/* global Autosave */
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore'; import _ from 'underscore';
import autosize from 'vendor/autosize'; import autosize from 'vendor/autosize';
import Flash from '../../flash'; import Flash from '../../flash';
import '../../autosave'; import Autosave from '../../autosave';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import * as constants from '../constants'; import * as constants from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
......
/* globals Autosave */ import Autosave from '../../autosave';
import '../../autosave';
export default { export default {
methods: { methods: {
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
@import "framework/secondary-navigation-elements"; @import "framework/secondary-navigation-elements";
@import "framework/selects"; @import "framework/selects";
@import "framework/sidebar"; @import "framework/sidebar";
@import "framework/new-sidebar"; @import "framework/contextual-sidebar";
@import "framework/tables"; @import "framework/tables";
@import "framework/notes"; @import "framework/notes";
@import "framework/tabs"; @import "framework/tabs";
......
<<<<<<< HEAD:app/assets/stylesheets/framework/new-sidebar.scss
@import "framework/variables"; @import "framework/variables";
@import 'framework/tw_bootstrap_variables'; @import 'framework/tw_bootstrap_variables';
@import "bootstrap/variables"; @import "bootstrap/variables";
...@@ -13,12 +14,15 @@ $new-sidebar-width: 220px; ...@@ -13,12 +14,15 @@ $new-sidebar-width: 220px;
$new-sidebar-collapsed-width: 50px; $new-sidebar-collapsed-width: 50px;
.page-with-new-sidebar { .page-with-new-sidebar {
=======
.page-with-contextual-sidebar {
>>>>>>> 1c317af47772ed2862a9c1ed22ab952bb1a84874:app/assets/stylesheets/framework/contextual-sidebar.scss
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
padding-left: $new-sidebar-collapsed-width; padding-left: $contextual-sidebar-collapsed-width;
} }
@media (min-width: $screen-lg-min) { @media (min-width: $screen-lg-min) {
padding-left: $new-sidebar-width; padding-left: $contextual-sidebar-width;
} }
// Override position: absolute // Override position: absolute
...@@ -34,7 +38,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -34,7 +38,7 @@ $new-sidebar-collapsed-width: 50px;
.page-with-icon-sidebar { .page-with-icon-sidebar {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-left: $new-sidebar-collapsed-width; padding-left: $contextual-sidebar-collapsed-width;
} }
} }
...@@ -52,12 +56,12 @@ $new-sidebar-collapsed-width: 50px; ...@@ -52,12 +56,12 @@ $new-sidebar-collapsed-width: 50px;
&:hover, &:hover,
a:hover { a:hover {
background-color: $hover-background; background-color: $link-hover-background;
color: $hover-color; color: $gl-text-color;
.settings-avatar { .settings-avatar {
svg { svg {
fill: $hover-color; fill: $gl-text-color;
} }
} }
} }
...@@ -85,7 +89,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -85,7 +89,7 @@ $new-sidebar-collapsed-width: 50px;
.nav-sidebar { .nav-sidebar {
position: fixed; position: fixed;
z-index: 400; z-index: 400;
width: $new-sidebar-width; width: $contextual-sidebar-width;
transition: left $sidebar-transition-duration; transition: left $sidebar-transition-duration;
top: $header-height; top: $header-height;
bottom: 0; bottom: 0;
...@@ -103,7 +107,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -103,7 +107,7 @@ $new-sidebar-collapsed-width: 50px;
&.sidebar-icons-only { &.sidebar-icons-only {
width: auto; width: auto;
min-width: $new-sidebar-collapsed-width; min-width: $contextual-sidebar-collapsed-width;
.nav-sidebar-inner-scroll { .nav-sidebar-inner-scroll {
overflow-x: hidden; overflow-x: hidden;
...@@ -149,11 +153,11 @@ $new-sidebar-collapsed-width: 50px; ...@@ -149,11 +153,11 @@ $new-sidebar-collapsed-width: 50px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 12px 16px; padding: 12px 16px;
color: $inactive-color; color: $gl-text-color-secondary;
} }
svg { svg {
fill: $inactive-color; fill: $gl-text-color-secondary;
} }
} }
...@@ -168,7 +172,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -168,7 +172,7 @@ $new-sidebar-collapsed-width: 50px;
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
left: (-$new-sidebar-width); left: (-$contextual-sidebar-width);
} }
.nav-icon-container { .nav-icon-container {
...@@ -210,8 +214,8 @@ $new-sidebar-collapsed-width: 50px; ...@@ -210,8 +214,8 @@ $new-sidebar-collapsed-width: 50px;
&:hover, &:hover,
&:focus { &:focus {
background: $active-hover-background; background: $link-active-background;
color: $active-hover-color; color: $gl-text-color;
} }
} }
...@@ -220,7 +224,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -220,7 +224,7 @@ $new-sidebar-collapsed-width: 50px;
&, &,
&:hover, &:hover,
&:focus { &:focus {
background: $active-background; background: $link-active-background;
} }
} }
} }
...@@ -308,11 +312,11 @@ $new-sidebar-collapsed-width: 50px; ...@@ -308,11 +312,11 @@ $new-sidebar-collapsed-width: 50px;
.badge { .badge {
background-color: $inactive-badge-background; background-color: $inactive-badge-background;
color: $inactive-color; color: $gl-text-color-secondary;
} }
&.active { &.active {
background: $active-background; background: $link-active-background;
> a { > a {
margin-left: 4px; margin-left: 4px;
...@@ -330,7 +334,11 @@ $new-sidebar-collapsed-width: 50px; ...@@ -330,7 +334,11 @@ $new-sidebar-collapsed-width: 50px;
&.active > a:hover, &.active > a:hover,
&.is-over > a { &.is-over > a {
<<<<<<< HEAD:app/assets/stylesheets/framework/new-sidebar.scss
background-color: $hover-background; background-color: $hover-background;
=======
background-color: $link-hover-background;
>>>>>>> 1c317af47772ed2862a9c1ed22ab952bb1a84874:app/assets/stylesheets/framework/contextual-sidebar.scss
} }
} }
} }
...@@ -340,7 +348,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -340,7 +348,7 @@ $new-sidebar-collapsed-width: 50px;
.toggle-sidebar-button, .toggle-sidebar-button,
.close-nav-button { .close-nav-button {
width: $new-sidebar-width - 2px; width: $contextual-sidebar-width - 2px;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
padding: 16px; padding: 16px;
...@@ -407,7 +415,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -407,7 +415,7 @@ $new-sidebar-collapsed-width: 50px;
} }
.toggle-sidebar-button { .toggle-sidebar-button {
width: $new-sidebar-collapsed-width - 2px; width: $contextual-sidebar-collapsed-width - 2px;
padding: 16px; padding: 16px;
.collapse-text, .collapse-text,
......
...@@ -9,6 +9,8 @@ $sidebar-transition-duration: .15s; ...@@ -9,6 +9,8 @@ $sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1024px; $sidebar-breakpoint: 1024px;
$default-transition-duration: .15s; $default-transition-duration: .15s;
$right-sidebar-transition-duration: .3s; $right-sidebar-transition-duration: .3s;
$contextual-sidebar-width: 220px;
$contextual-sidebar-collapsed-width: 50px;
/* /*
* Color schema * Color schema
...@@ -365,6 +367,13 @@ $dropdown-item-hover-bg: $gray-darker; ...@@ -365,6 +367,13 @@ $dropdown-item-hover-bg: $gray-darker;
$filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09); $filtered-search-term-shadow-color: rgba(0, 0, 0, 0.09);
$dropdown-hover-color: $blue-400; $dropdown-hover-color: $blue-400;
/*
* Contextual Sidebar
*/
$link-active-background: rgba(0, 0, 0, .04);
$link-hover-background: rgba(0, 0, 0, .06);
$inactive-badge-background: rgba(0, 0, 0, .08);
/* /*
* Buttons * Buttons
*/ */
...@@ -411,7 +420,6 @@ $note-targe3-inside: #ffffd3; ...@@ -411,7 +420,6 @@ $note-targe3-inside: #ffffd3;
$note-line2-border: #ddd; $note-line2-border: #ddd;
$note-icon-gutter-width: 55px; $note-icon-gutter-width: 55px;
/* /*
* Zen * Zen
*/ */
......
...@@ -476,7 +476,7 @@ ...@@ -476,7 +476,7 @@
border-top: 1px solid $border-color; border-top: 1px solid $border-color;
} }
.page-with-new-sidebar.page-with-sidebar .issue-boards-sidebar { .page-with-contextual-sidebar.page-with-sidebar .issue-boards-sidebar {
.issuable-sidebar-header { .issuable-sidebar-header {
position: relative; position: relative;
} }
......
...@@ -121,6 +121,15 @@ module ApplicationSettingsHelper ...@@ -121,6 +121,15 @@ module ApplicationSettingsHelper
message.html_safe message.html_safe
end end
def circuitbreaker_access_retries_help_text
_('The number of attempts GitLab will make to access a storage.')
end
def circuitbreaker_backoff_threshold_help_text
_("The number of failures after which GitLab will start temporarily "\
"disabling access to a storage shard on a host")
end
def circuitbreaker_failure_wait_time_help_text def circuitbreaker_failure_wait_time_help_text
_("When access to a storage fails. GitLab will prevent access to the "\ _("When access to a storage fails. GitLab will prevent access to the "\
"storage for the time specified here. This allows the filesystem to "\ "storage for the time specified here. This allows the filesystem to "\
...@@ -145,6 +154,8 @@ module ApplicationSettingsHelper ...@@ -145,6 +154,8 @@ module ApplicationSettingsHelper
:akismet_api_key, :akismet_api_key,
:akismet_enabled, :akismet_enabled,
:auto_devops_enabled, :auto_devops_enabled,
:circuitbreaker_access_retries,
:circuitbreaker_backoff_threshold,
:circuitbreaker_failure_count_threshold, :circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_reset_time, :circuitbreaker_failure_reset_time,
:circuitbreaker_failure_wait_time, :circuitbreaker_failure_wait_time,
......
module NavHelper module NavHelper
def page_with_sidebar_class def page_with_sidebar_class
class_name = page_gutter_class class_name = page_gutter_class
class_name << 'page-with-new-sidebar' if defined?(@left_sidebar) && @left_sidebar class_name << 'page-with-contextual-sidebar' if defined?(@left_sidebar) && @left_sidebar
class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar
class_name class_name
......
...@@ -16,17 +16,16 @@ module StorageHealthHelper ...@@ -16,17 +16,16 @@ module StorageHealthHelper
def message_for_circuit_breaker(circuit_breaker) def message_for_circuit_breaker(circuit_breaker)
maximum_failures = circuit_breaker.failure_count_threshold maximum_failures = circuit_breaker.failure_count_threshold
current_failures = circuit_breaker.failure_count current_failures = circuit_breaker.failure_count
permanently_broken = circuit_breaker.circuit_broken? && current_failures >= maximum_failures
translation_params = { number_of_failures: current_failures, translation_params = { number_of_failures: current_failures,
maximum_failures: maximum_failures, maximum_failures: maximum_failures,
number_of_seconds: circuit_breaker.failure_wait_time } number_of_seconds: circuit_breaker.failure_wait_time }
if permanently_broken if circuit_breaker.circuit_broken?
s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\ s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
"retry automatically. Reset storage information when the problem is "\ "retry automatically. Reset storage information when the problem is "\
"resolved.") % translation_params "resolved.") % translation_params
elsif circuit_breaker.circuit_broken? elsif circuit_breaker.backing_off?
_("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\ _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
"block access for %{number_of_seconds} seconds.") % translation_params "block access for %{number_of_seconds} seconds.") % translation_params
else else
......
...@@ -166,13 +166,25 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -166,13 +166,25 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
numericality: { greater_than_or_equal_to: 0 } numericality: { greater_than_or_equal_to: 0 }
validates :circuitbreaker_failure_count_threshold, validates :circuitbreaker_backoff_threshold,
:circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_wait_time, :circuitbreaker_failure_wait_time,
:circuitbreaker_failure_reset_time, :circuitbreaker_failure_reset_time,
:circuitbreaker_storage_timeout, :circuitbreaker_storage_timeout,
presence: true, presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 } numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :circuitbreaker_access_retries,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1 }
validates_each :circuitbreaker_backoff_threshold do |record, attr, value|
if value.to_i >= record.circuitbreaker_failure_count_threshold
record.errors.add(attr, _("The circuitbreaker backoff threshold should be "\
"lower than the failure count threshold"))
end
end
SUPPORTED_KEY_TYPES.each do |type| SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type } validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end end
......
...@@ -571,11 +571,23 @@ ...@@ -571,11 +571,23 @@
%fieldset %fieldset
%legend Git Storage Circuitbreaker settings %legend Git Storage Circuitbreaker settings
.form-group .form-group
= f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2' = f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control' = f.number_field :circuitbreaker_access_retries, class: 'form-control'
.help-block .help-block
= circuitbreaker_failure_count_help_text = circuitbreaker_access_retries_help_text
.form-group
= f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_storage_timeout, class: 'form-control'
.help-block
= circuitbreaker_storage_timeout_help_text
.form-group
= f.label :circuitbreaker_backoff_threshold, _('Number of failures before backing off'), class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :circuitbreaker_backoff_threshold, class: 'form-control'
.help-block
= circuitbreaker_backoff_threshold_help_text
.form-group .form-group
= f.label :circuitbreaker_failure_wait_time, _('Seconds to wait after a storage failure'), class: 'control-label col-sm-2' = f.label :circuitbreaker_failure_wait_time, _('Seconds to wait after a storage failure'), class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
...@@ -583,17 +595,17 @@ ...@@ -583,17 +595,17 @@
.help-block .help-block
= circuitbreaker_failure_wait_time_help_text = circuitbreaker_failure_wait_time_help_text
.form-group .form-group
= f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'control-label col-sm-2' = f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.number_field :circuitbreaker_failure_reset_time, class: 'form-control' = f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
.help-block .help-block
= circuitbreaker_failure_reset_time_help_text = circuitbreaker_failure_count_help_text
.form-group .form-group
= f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'control-label col-sm-2' = f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.number_field :circuitbreaker_storage_timeout, class: 'form-control' = f.number_field :circuitbreaker_failure_reset_time, class: 'form-control'
.help-block .help-block
= circuitbreaker_storage_timeout_help_text = circuitbreaker_failure_reset_time_help_text
%fieldset %fieldset
%legend Repository Checks %legend Repository Checks
......
---
title: Make the circuitbreaker more robust by adding higher thresholds, and multiple
access attempts.
merge_request: 14933
author:
type: fixed
class AddNewCircuitbreakerSettingsToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :application_settings,
:circuitbreaker_access_retries,
:integer,
default: 3
add_column :application_settings,
:circuitbreaker_backoff_threshold,
:integer,
default: 80
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20171017130239) do ActiveRecord::Schema.define(version: 20171017145932) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -162,6 +162,8 @@ ActiveRecord::Schema.define(version: 20171017130239) do ...@@ -162,6 +162,8 @@ ActiveRecord::Schema.define(version: 20171017130239) do
t.integer "circuitbreaker_failure_reset_time", default: 1800 t.integer "circuitbreaker_failure_reset_time", default: 1800
t.integer "circuitbreaker_storage_timeout", default: 30 t.integer "circuitbreaker_storage_timeout", default: 30
t.boolean "remote_mirror_available", default: true, null: false t.boolean "remote_mirror_available", default: true, null: false
t.integer "circuitbreaker_access_retries", default: 3
t.integer "circuitbreaker_backoff_threshold", default: 80
end end
create_table "approvals", force: :cascade do |t| create_table "approvals", force: :cascade do |t|
......
...@@ -376,10 +376,6 @@ Select one node as a primary node. ...@@ -376,10 +376,6 @@ Select one node as a primary node.
CREATE EXTENSION pg_trgm; CREATE EXTENSION pg_trgm;
``` ```
# Output:
CREATE EXTENSION
1. Exit the database prompt by typing `\q` and Enter. 1. Exit the database prompt by typing `\q` and Enter.
1. Verify the cluster is initialized with one node: 1. Verify the cluster is initialized with one node:
......
...@@ -109,6 +109,11 @@ This can be configured from the admin interface: ...@@ -109,6 +109,11 @@ This can be configured from the admin interface:
![circuitbreaker configuration](img/circuitbreaker_config.png) ![circuitbreaker configuration](img/circuitbreaker_config.png)
**Number of access attempts**: The number of attempts GitLab will make to access a
storage when probing a shard.
**Number of failures before backing off**: The number of failures after which
GitLab will start temporarily disabling access to a storage shard on a host.
**Maximum git storage failures:** The number of failures of after which GitLab will **Maximum git storage failures:** The number of failures of after which GitLab will
completely prevent access to the storage. The number of failures can be reset in completely prevent access to the storage. The number of failures can be reset in
...@@ -126,6 +131,15 @@ mount is reset. ...@@ -126,6 +131,15 @@ mount is reset.
**Seconds to wait for a storage access attempt:** The time in seconds GitLab will **Seconds to wait for a storage access attempt:** The time in seconds GitLab will
try to access storage. After this time a timeout error will be raised. try to access storage. After this time a timeout error will be raised.
To enable the circuitbreaker for repository storage you can flip the feature flag from a rails console:
```
Feature.enable('git_storage_circuit_breaker')
```
Alternatively it can be enabled by setting `true` in the `GIT_STORAGE_CIRCUIT_BREAKER` environment variable.
This approach would be used when enabling the circuit breaker on a single host.
When storage failures occur, this will be visible in the admin interface like this: When storage failures occur, this will be visible in the admin interface like this:
![failing storage](img/failing_storage.png) ![failing storage](img/failing_storage.png)
......
...@@ -72,6 +72,8 @@ PUT /application/settings ...@@ -72,6 +72,8 @@ PUT /application/settings
| `akismet_enabled` | boolean | no | Enable or disable akismet spam protection | | `akismet_enabled` | boolean | no | Enable or disable akismet spam protection |
| `allow_group_owners_to_manage_ldap` | boolean | no | Set to `true` to allow group owners to manage LDAP | | `allow_group_owners_to_manage_ldap` | boolean | no | Set to `true` to allow group owners to manage LDAP |
| `authorized_keys_enabled` | boolean | no | By default, we write to the "authorized_keys" file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. | | `authorized_keys_enabled` | boolean | no | By default, we write to the "authorized_keys" file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. |
| `circuitbreaker_access_retries | integer | no | The number of attempts GitLab will make to access a storage. |
| `circuitbreaker_backoff_threshold | integer | no | The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host. |
| `circuitbreaker_failure_count_threshold` | integer | no | The number of failures of after which GitLab will completely prevent access to the storage. | | `circuitbreaker_failure_count_threshold` | integer | no | The number of failures of after which GitLab will completely prevent access to the storage. |
| `circuitbreaker_failure_reset_time` | integer | no | Time in seconds GitLab will keep storage failure information. When no failures occur during this time, the failure information is reset. | | `circuitbreaker_failure_reset_time` | integer | no | Time in seconds GitLab will keep storage failure information. When no failures occur during this time, the failure information is reset. |
| `circuitbreaker_failure_wait_time` | integer | no | Time in seconds GitLab will block access to a failing storage to allow it to recover. | | `circuitbreaker_failure_wait_time` | integer | no | Time in seconds GitLab will block access to a failing storage to allow it to recover. |
......
...@@ -190,7 +190,11 @@ used for all GitLab Geo installations. ...@@ -190,7 +190,11 @@ used for all GitLab Geo installations.
you created previously. **Do NOT** check the box 'This is a primary node'. you created previously. **Do NOT** check the box 'This is a primary node'.
1. Added in GitLab 9.5: Choose which namespaces should be replicated by the secondary node. Leave blank to replicate all. Read more in [selective replication](#selective-replication). 1. Added in GitLab 9.5: Choose which namespaces should be replicated by the secondary node. Leave blank to replicate all. Read more in [selective replication](#selective-replication).
1. Click the **Add node** button. 1. Click the **Add node** button.
1. Restart GitLab on the secondary:
```
gitlab-ctl restart
```
--- ---
After the **Add Node** button is pressed, the primary node will start to notify After the **Add Node** button is pressed, the primary node will start to notify
......
...@@ -186,41 +186,16 @@ The following guide assumes that: ...@@ -186,41 +186,16 @@ The following guide assumes that:
geo_secondary_role['enable'] = true geo_secondary_role['enable'] = true
``` ```
1. Optional since GitLab 9.1, and required for GitLab 10.0 or higher: 1. [Reconfigure GitLab][] for the changes to take effect.
[Enable tracking database on the secondary server](#enable-tracking-database-on-the-secondary-server)
1. Otherwise, continue to [initiate the replication process](#step-3-initiate-the-replication-process).
#### Enable tracking database on the secondary server
Geo secondary nodes use a tracking database to keep track of replication status and recover
automatically from some replication issues.
It is added in GitLab 9.1, and since GitLab 10.0 it is required.
> **IMPORTANT:** For this feature to work correctly, all nodes must be
with their clocks synchronized. It is not required for all nodes to be set to
the same time zone, but when the respective times are converted to UTC time,
the clocks must be synchronized to within 60 seconds of each other.
1. Setup clock synchronization service in your Linux distro. 1. Setup clock synchronization service in your Linux distro.
This can easily be done via any NTP-compatible daemon. For example, This can easily be done via any NTP-compatible daemon. For example,
here are [instructions for setting up NTP with Ubuntu](https://help.ubuntu.com/lts/serverguide/NTP.html). here are [instructions for setting up NTP with Ubuntu](https://help.ubuntu.com/lts/serverguide/NTP.html).
1. Edit `/etc/gitlab/gitlab.rb` and add the following: **IMPORTANT:** For Geo to work correctly, all nodes must be with their
clocks synchronized. It is not required for all nodes to be set to the
```ruby same time zone, but when the respective times are converted to UTC time,
geo_postgresql['enable'] = true the clocks must be synchronized to within 60 seconds of each other.
```
1. Set up the Geo tracking database:
```
sudo gitlab-rake geo:db:migrate
```
1. [Reconfigure GitLab][] for the changes to take effect.
1. Continue to [initiate the replication process](#step-3-initiate-the-replication-process).
### Step 3. Initiate the replication process ### Step 3. Initiate the replication process
......
...@@ -40,6 +40,8 @@ it was taken care of closely throughout the whole quarter ...@@ -40,6 +40,8 @@ it was taken care of closely throughout the whole quarter
## How it works ## How it works
>**Note:** Burndown charts are only available for project milestones. They will be available for group milestones [in the future](https://gitlab.com/gitlab-org/gitlab-ee/issues/3064).
A Burndown Chart is available for every project milestone that has been attributed a **start A Burndown Chart is available for every project milestone that has been attributed a **start
date** and a **due date**. date** and a **due date**.
......
...@@ -12,6 +12,7 @@ module Gitlab ...@@ -12,6 +12,7 @@ module Gitlab
CircuitOpen = Class.new(Inaccessible) CircuitOpen = Class.new(Inaccessible)
Misconfiguration = Class.new(Inaccessible) Misconfiguration = Class.new(Inaccessible)
Failing = Class.new(Inaccessible)
REDIS_KEY_PREFIX = 'storage_accessible:'.freeze REDIS_KEY_PREFIX = 'storage_accessible:'.freeze
......
...@@ -54,7 +54,7 @@ module Gitlab ...@@ -54,7 +54,7 @@ module Gitlab
end end
def perform def perform
return yield unless Feature.enabled?('git_storage_circuit_breaker') return yield unless enabled?
check_storage_accessible! check_storage_accessible!
...@@ -64,10 +64,27 @@ module Gitlab ...@@ -64,10 +64,27 @@ module Gitlab
def circuit_broken? def circuit_broken?
return false if no_failures? return false if no_failures?
failure_count > failure_count_threshold
end
def backing_off?
return false if no_failures?
recent_failure = last_failure > failure_wait_time.seconds.ago recent_failure = last_failure > failure_wait_time.seconds.ago
too_many_failures = failure_count > failure_count_threshold too_many_failures = failure_count > backoff_threshold
recent_failure || too_many_failures recent_failure && too_many_failures
end
private
# The circuitbreaker can be enabled for the entire fleet using a Feature
# flag.
#
# Enabling it for a single host can be done setting the
# `GIT_STORAGE_CIRCUIT_BREAKER` environment variable.
def enabled?
ENV['GIT_STORAGE_CIRCUIT_BREAKER'].present? || Feature.enabled?('git_storage_circuit_breaker')
end end
def failure_info def failure_info
...@@ -83,7 +100,7 @@ module Gitlab ...@@ -83,7 +100,7 @@ module Gitlab
return @storage_available if @storage_available return @storage_available if @storage_available
if @storage_available = Gitlab::Git::Storage::ForkedStorageCheck if @storage_available = Gitlab::Git::Storage::ForkedStorageCheck
.storage_available?(storage_path, storage_timeout) .storage_available?(storage_path, storage_timeout, access_retries)
track_storage_accessible track_storage_accessible
else else
track_storage_inaccessible track_storage_inaccessible
...@@ -94,7 +111,11 @@ module Gitlab ...@@ -94,7 +111,11 @@ module Gitlab
def check_storage_accessible! def check_storage_accessible!
if circuit_broken? if circuit_broken?
raise Gitlab::Git::Storage::CircuitOpen.new("Circuit for #{storage} is broken", failure_wait_time) raise Gitlab::Git::Storage::CircuitOpen.new("Circuit for #{storage} is broken", failure_reset_time)
end
if backing_off?
raise Gitlab::Git::Storage::Failing.new("Backing off access to #{storage}", failure_wait_time)
end end
unless storage_available? unless storage_available?
...@@ -131,12 +152,6 @@ module Gitlab ...@@ -131,12 +152,6 @@ module Gitlab
end end
end end
def cache_key
@cache_key ||= "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage}:#{hostname}"
end
private
def get_failure_info def get_failure_info
last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis| last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis|
redis.hmget(cache_key, :last_failure, :failure_count) redis.hmget(cache_key, :last_failure, :failure_count)
...@@ -146,6 +161,10 @@ module Gitlab ...@@ -146,6 +161,10 @@ module Gitlab
FailureInfo.new(last_failure, failure_count.to_i) FailureInfo.new(last_failure, failure_count.to_i)
end end
def cache_key
@cache_key ||= "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage}:#{hostname}"
end
end end
end end
end end
......
...@@ -18,6 +18,14 @@ module Gitlab ...@@ -18,6 +18,14 @@ module Gitlab
application_settings.circuitbreaker_storage_timeout application_settings.circuitbreaker_storage_timeout
end end
def access_retries
application_settings.circuitbreaker_access_retries
end
def backoff_threshold
application_settings.circuitbreaker_backoff_threshold
end
private private
def application_settings def application_settings
......
...@@ -4,8 +4,17 @@ module Gitlab ...@@ -4,8 +4,17 @@ module Gitlab
module ForkedStorageCheck module ForkedStorageCheck
extend self extend self
def storage_available?(path, timeout_seconds = 5) def storage_available?(path, timeout_seconds = 5, retries = 1)
status = timeout_check(path, timeout_seconds) partial_timeout = timeout_seconds / retries
status = timeout_check(path, partial_timeout)
# If the status check did not succeed the first time, we retry a few
# more times to avoid one-off failures
current_attempts = 1
while current_attempts < retries && !status.success?
status = timeout_check(path, partial_timeout)
current_attempts += 1
end
status.success? status.success?
end end
......
...@@ -25,6 +25,10 @@ module Gitlab ...@@ -25,6 +25,10 @@ module Gitlab
!!@error !!@error
end end
def backing_off?
false
end
def last_failure def last_failure
circuit_broken? ? Time.now : nil circuit_broken? ? Time.now : nil
end end
......
...@@ -33,6 +33,21 @@ describe Gitlab::Git::Storage::ForkedStorageCheck, broken_storage: true, skip_da ...@@ -33,6 +33,21 @@ describe Gitlab::Git::Storage::ForkedStorageCheck, broken_storage: true, skip_da
expect(runtime).to be < 1.0 expect(runtime).to be < 1.0
end end
it 'will try the specified amount of times before failing' do
allow(described_class).to receive(:check_filesystem_in_process) do
Process.spawn("sleep 10")
end
expect(Process).to receive(:spawn).with('sleep 10').twice
.and_call_original
runtime = Benchmark.realtime do
described_class.storage_available?(existing_path, 0.5, 2)
end
expect(runtime).to be < 1.0
end
describe 'when using paths with spaces' do describe 'when using paths with spaces' do
let(:test_dir) { Rails.root.join('tmp', 'tests', 'storage_check') } let(:test_dir) { Rails.root.join('tmp', 'tests', 'storage_check') }
let(:path_with_spaces) { File.join(test_dir, 'path with spaces') } let(:path_with_spaces) { File.join(test_dir, 'path with spaces') }
......
...@@ -65,17 +65,6 @@ describe Gitlab::Git::Storage::NullCircuitBreaker do ...@@ -65,17 +65,6 @@ describe Gitlab::Git::Storage::NullCircuitBreaker do
ours = described_class.public_instance_methods ours = described_class.public_instance_methods
theirs = Gitlab::Git::Storage::CircuitBreaker.public_instance_methods theirs = Gitlab::Git::Storage::CircuitBreaker.public_instance_methods
# These methods are not part of the public API, but are public to allow the expect(theirs - ours).to be_empty
# CircuitBreaker specs to operate. They should be made private over time.
exceptions = %i[
cache_key
check_storage_accessible!
no_failures?
storage_available?
track_storage_accessible
track_storage_inaccessible
]
expect(theirs - ours).to contain_exactly(*exceptions)
end end
end end
...@@ -115,7 +115,8 @@ describe ApplicationSetting do ...@@ -115,7 +115,8 @@ describe ApplicationSetting do
end end
context 'circuitbreaker settings' do context 'circuitbreaker settings' do
[:circuitbreaker_failure_count_threshold, [:circuitbreaker_backoff_threshold,
:circuitbreaker_failure_count_threshold,
:circuitbreaker_failure_wait_time, :circuitbreaker_failure_wait_time,
:circuitbreaker_failure_reset_time, :circuitbreaker_failure_reset_time,
:circuitbreaker_storage_timeout].each do |field| :circuitbreaker_storage_timeout].each do |field|
...@@ -125,6 +126,16 @@ describe ApplicationSetting do ...@@ -125,6 +126,16 @@ describe ApplicationSetting do
.is_greater_than_or_equal_to(0) .is_greater_than_or_equal_to(0)
end end
end end
it 'requires the `backoff_threshold` to be lower than the `failure_count_threshold`' do
setting.circuitbreaker_failure_count_threshold = 10
setting.circuitbreaker_backoff_threshold = 15
failure_message = "The circuitbreaker backoff threshold should be lower "\
"than the failure count threshold"
expect(setting).not_to be_valid
expect(setting.errors[:circuitbreaker_backoff_threshold]).to include(failure_message)
end
end end
context 'repository storages' do context 'repository storages' do
......
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