Skip to content

Commit

Permalink
Merge pull request #897 from mzazrivec/add_new_options_for_migration_…
Browse files Browse the repository at this point in the history
…throttling

Add new options for migration throttling

(cherry picked from commit 5cee58d)

https://bugzilla.redhat.com/show_bug.cgi?id=1693746
  • Loading branch information
mturley authored and simaishi committed Apr 5, 2019
1 parent 4f06604 commit 9b2a950
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 12 deletions.
5 changes: 5 additions & 0 deletions app/javascript/react/screens/App/Settings/Settings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@
min-width: 150px;
text-align: right;
}

.postfix-label {
background-color: #fafafa;
border-left-width: 0px;
}
14 changes: 12 additions & 2 deletions app/javascript/react/screens/App/Settings/helpers.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { FINISHED, ERROR } from './screens/ConversionHostsSettings/ConversionHostsSettingsConstants';

export const getFormValuesFromApiSettings = payload => ({
max_concurrent_tasks_per_host: payload.transformation.limits.max_concurrent_tasks_per_host
max_concurrent_tasks_per_host: payload.transformation.limits.max_concurrent_tasks_per_host,
max_concurrent_tasks_per_ems: payload.transformation.limits.max_concurrent_tasks_per_ems
/* FIXME: remove the comment once backend is ready
cpu_limit_per_host: payload.transformation.limits.cpu_limit_per_host,
network_limit_per_host: payload.transformation.limits.network_limit_per_host
*/
});

export const getApiSettingsFromFormValues = values => ({
transformation: {
limits: {
max_concurrent_tasks_per_host: values.max_concurrent_tasks_per_host
max_concurrent_tasks_per_host: values.max_concurrent_tasks_per_host,
max_concurrent_tasks_per_ems: values.max_concurrent_tasks_per_ems
/* FIXME: remove the comment once backend is ready
cpu_limit_per_host: values.cpu_limit_per_host,
network_limit_per_host: values.network_limit_per_host
*/
}
}
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { reduxForm, Field } from 'redux-form';
import { Form, Button, Spinner } from 'patternfly-react';
import { Form, Button, Icon, OverlayTrigger, Popover, Spinner } from 'patternfly-react';
import NumberInput from '../../../common/forms/NumberInput';
// import TextInputWithCheckbox from '../../../common/forms/TextInputWithCheckbox'; // FIXME: uncomment once backend is ready

const FORM_NAME = 'settings';

export class GeneralSettings extends React.Component {
componentDidMount() {
Expand All @@ -16,6 +19,20 @@ export class GeneralSettings extends React.Component {
patchSettingsAction(servers, settingsForm.values);
};

enforceConstraintsOnChange = (event, newValue, prevValue, fieldChanging) => {
const {
settingsForm: {
values: { max_concurrent_tasks_per_host, max_concurrent_tasks_per_ems }
},
formChangeAction
} = this.props;
if (fieldChanging === 'max_concurrent_tasks_per_host' && newValue > max_concurrent_tasks_per_ems) {
formChangeAction(FORM_NAME, 'max_concurrent_tasks_per_ems', newValue);
} else if (fieldChanging === 'max_concurrent_tasks_per_ems' && newValue < max_concurrent_tasks_per_host) {
formChangeAction(FORM_NAME, 'max_concurrent_tasks_per_host', newValue);
}
};

render() {
const { isFetchingServers, isFetchingSettings, isSavingSettings, savedSettings, settingsForm } = this.props;

Expand All @@ -24,27 +41,99 @@ export class GeneralSettings extends React.Component {
settingsForm.values &&
Object.keys(savedSettings).some(key => savedSettings[key] !== settingsForm.values[key]);

// const inputEnabledFunction = value => value !== 'unlimited'; // FIXME: uncomment once backend is ready

return (
<Spinner loading={isFetchingServers || isFetchingSettings} style={{ marginTop: 15 }}>
<div className="migration-settings">
<Form style={{ padding: '0 20px' }}>
<Form className="form-horizontal" style={{ padding: '0 20px' }}>
<div>
<h3>{__('Concurrent Migrations')}</h3>
</div>
<Form.FormGroup>
<Form.ControlLabel>{__('Maximum concurrent migrations per conversion host')}</Form.ControlLabel>
<div style={{ width: 100 }}>
<Form.ControlLabel className="col-md-5">
<span className="pull-left">
{__('Maximum concurrent migrations per conversion host')}
<OverlayTrigger
overlay={
<Popover id="maximum_concurrect_migrations_per_provider_popover">
{__(
'For VDDK transformations the maximum concurrent migrations per conversion host is limited to 20. See the product documentation for more information.'
)}
</Popover>
}
placement="top"
trigger={['hover']}
delay={500}
rootClose={false}
>
<Icon
type="pf"
name="info"
size="md"
style={{
width: 'inherit',
backgroundColor: 'transparent',
padding: 10
}}
/>
</OverlayTrigger>
</span>
</Form.ControlLabel>
<div className="col-md-2">
<Field
id="max_concurrent_tasks_per_host"
name="max_concurrent_tasks_per_host"
component={NumberInput}
normalize={NumberInput.normalizeStringToInt}
min={1}
onChange={this.enforceConstraintsOnChange}
/>
</div>
</Form.FormGroup>
<Form.FormGroup>
<Form.ControlLabel className="col-md-5">
<div className="pull-left">{__('Maximum concurrent migrations per provider')}</div>
</Form.ControlLabel>
<div className="col-md-2">
<Field
id="max_concurrent_tasks_per_ems"
name="max_concurrent_tasks_per_ems"
component={NumberInput}
normalize={NumberInput.normalizeStringToInt}
min={1}
onChange={this.enforceConstraintsOnChange}
/>
</div>
</Form.FormGroup>
{/* FIXME: uncomment once backend is ready
<Form.FormGroup />
<div>
<h3>{__('Resource Utilization Limits for Migrations')}</h3>
</div>
<Field
id="cpu_limit_per_host"
name="cpu_limit_per_host"
component={TextInputWithCheckbox}
normalize={TextInputWithCheckbox.normalizeStringToInt}
label={__('Max CPU utilization per conversion host')}
postfix="%"
inputEnabledFunction={inputEnabledFunction}
/>
<Field
id="network_limit_per_host"
name="network_limit_per_host"
component={TextInputWithCheckbox}
normalize={TextInputWithCheckbox.normalizeStringToInt}
label={__('Total network throughput')}
postfix={__('MB/s')}
inputEnabledFunction={inputEnabledFunction}
/>
*/}
<Form.FormGroup className="col-md-1 pull-left" style={{ marginTop: '40px' }}>
<Button bsStyle="primary" onClick={this.onApplyClick} disabled={!hasUnsavedChanges || isSavingSettings}>
{__('Apply')}
</Button>
<br />
{isSavingSettings && (
<div style={{ paddingTop: 10 }}>
<Spinner loading size="xs" inline />
Expand All @@ -70,7 +159,8 @@ GeneralSettings.propTypes = {
savedSettings: PropTypes.object,
settingsForm: PropTypes.object,
fetchServersUrl: PropTypes.string,
fetchSettingsUrl: PropTypes.string
fetchSettingsUrl: PropTypes.string,
formChangeAction: PropTypes.func
};

GeneralSettings.defaultProps = {
Expand All @@ -79,5 +169,5 @@ GeneralSettings.defaultProps = {
};

export default reduxForm({
form: 'settings'
form: FORM_NAME
})(GeneralSettings);
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { connect } from 'react-redux';
import { change } from 'redux-form';
import GeneralSettings from './GeneralSettings';

import * as SettingsActions from '../../SettingsActions';
import * as RouterActions from '../../../../../../redux/actions/routerActions';
import { fetchServersAction, fetchSettingsAction, patchSettingsAction } from '../../SettingsActions';

const mapStateToProps = ({ settings, form }, ownProps) => ({
...settings,
Expand All @@ -17,6 +17,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(stateP

export default connect(
mapStateToProps,
Object.assign(SettingsActions, RouterActions),
{ fetchServersAction, fetchSettingsAction, patchSettingsAction, formChangeAction: change },
mergeProps
)(GeneralSettings);
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Form } from 'patternfly-react';

class TextInputWithCheckbox extends React.Component {
constructor(props) {
super(props);
this.state = {
inputEnabled: props.inputEnabledFunction(props.input.value)
};
}

render() {
const {
id,
label,
input: { value, onChange },
postfix
} = this.props;

const onCheckboxChange = event => this.setState({ inputEnabled: event.target.checked });

return (
<Form.FormGroup>
<Form.ControlLabel className="col-md-4">
<div className="checkbox-inline pull-left">
<label>
<input
type="checkbox"
name={`${id}_checkbox`}
id={`${id}_checkbox`}
defaultChecked={this.state.inputEnabled}
onChange={event => onCheckboxChange(event)}
/>
{label}
</label>
</div>
</Form.ControlLabel>
<div className="col-md-2">
<div className="input-group">
<input
type="text"
className="form-control"
name={id}
id={id}
defaultValue={value}
readOnly={!this.state.inputEnabled}
onChange={event => onChange(event.target.value)}
/>
<div className="input-group-addon postfix-label">{postfix}</div>
</div>
</div>
</Form.FormGroup>
);
}
}

TextInputWithCheckbox.propTypes = {
id: PropTypes.string.isRequired,
input: PropTypes.shape({
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
onChange: PropTypes.func
}),
label: PropTypes.string.isRequired,
postfix: PropTypes.string,
inputEnabledFunction: PropTypes.func.isRequired
};

TextInputWithCheckbox.normalizeStringToInt = str => (str && parseInt(str.replace(/\D/g, ''), 10)) || 0;

export default TextInputWithCheckbox;

0 comments on commit 9b2a950

Please sign in to comment.