Skip to content

Commit

Permalink
Merge pull request #3839 from irosenzw/add-validation-feature
Browse files Browse the repository at this point in the history
Add memory validations in the VM wizard
  • Loading branch information
openshift-merge-robot committed Jan 6, 2020
2 parents 4cb2334 + 9d7481f commit 4ac553e
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { getRelevantTemplates } from '../../../../../selectors/vm-template/selectors';
import { getFieldId } from '../../../utils/vm-settings-tab-utils';
import { UpdateOptions } from '../../types';
import {
iGetVmSettingAttribute,
iGetVmSettingValue,
} from '../../../selectors/immutable/vm-settings';
import { iGetLoadedCommonData, iGetName } from '../../../selectors/immutable/selectors';
import { VMSettingsField, VMWizardProps, VMSettingsRenderableField } from '../../../types';
import { iGetIn } from '../../../../../utils/immutable';
import { CommonTemplatesValidation } from './validation-types';
import { getValidationsFromTemplates, getFieldValidations } from './selectors';

// TODO: Add all the fields in the form
// For each field we need to check for validations
const IDToJsonPath = {
[getFieldId(VMSettingsField.MEMORY)]: '.spec.domain.resources.requests.memory',
[getFieldId(VMSettingsField.CPU)]: '.spec.domain.cpu.cores',
};

export const getTemplateValidations = (
options: UpdateOptions,
fieldId: VMSettingsRenderableField,
): CommonTemplatesValidation[] => {
// Proceed if there are no validation for the specific fieldId
if (!(fieldId in IDToJsonPath)) {
return [];
}
const { getState, id } = options;
const state = getState();

// Get OS, workload-profile and flavor attributes
const os = iGetVmSettingAttribute(state, id, VMSettingsField.OPERATING_SYSTEM);
const flavor = iGetVmSettingAttribute(state, id, VMSettingsField.FLAVOR);
const workloadProfile = iGetVmSettingAttribute(state, id, VMSettingsField.WORKLOAD_PROFILE);

// Get all the templates from common-templates
const commonTemplates = iGetLoadedCommonData(state, id, VMWizardProps.commonTemplates);

// Get userTemplate if it was chosen
const userTemplateName = iGetVmSettingValue(state, id, VMSettingsField.USER_TEMPLATE);
const iUserTemplates = iGetLoadedCommonData(state, id, VMWizardProps.userTemplates);
const iUserTemplate =
userTemplateName && iUserTemplates
? iUserTemplates.find((template) => iGetName(template) === userTemplateName)
: null;

// Get all the validations from the relevant templates:
// Get the validations from the user template, if chosen.
// If not, get the validations from Common-Templates based on OS, Workload-Profile and Flavor
const validations = iUserTemplate
? JSON.parse(iGetIn(iUserTemplate, ['metadata', 'annotations', 'validations']))
: getValidationsFromTemplates(
// Get templates based on OS, Workload-Profile and Flavor
getRelevantTemplates(commonTemplates, os, workloadProfile, flavor),
);

// Return all the validations which are relevant for the field
return getFieldValidations(validations, IDToJsonPath[fieldId]);
};

export const runValidation = (
/*
- Check if at least one validation has passed.
- Would *NOT* check if all the validations have passed since it may not be possible
For example:
If we have two 'between' validations: 2-4 and 6-10 from 2 different templates,
There is no value that would satisfy both validations.
*/
validations: CommonTemplatesValidation[],
value: any,
): { isValid: boolean; errorMsg: string } => {
let errorMsg = null;
const isValid = validations.some((validation) => {
errorMsg = validation.message;
if ('min' in validation && 'max' in validation) {
return value <= validation.max && value >= validation.min;
}
if ('min' in validation) {
return value >= validation.min;
}
if ('max' in validation) {
return value <= validation.max;
}
return false;
});

return { isValid, errorMsg };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { iGetIn } from '../../../../../utils/immutable';
import { CommonTemplatesValidation } from './validation-types';

export const getValidationsFromTemplates = (templates): CommonTemplatesValidation[] => {
return templates
.map((relevantTemplate) =>
JSON.parse(iGetIn(relevantTemplate, ['metadata', 'annotations', 'validations'])),
)
.valueSeq()
.toArray()
.flat();
};

export const getFieldValidations = (
validations: CommonTemplatesValidation[],
jsonPath: string,
): CommonTemplatesValidation[] => {
return validations.filter((validation) => validation.path.includes(jsonPath));
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
enum commonTemplatesValidationRules {
integer = 'integer',
string = 'string',
regex = 'regex',
enum = 'enum',
}

export type CommonTemplatesValidation = {
name: string; // Identifier of the rule. Must be unique among all the rules attached to a template
rule: commonTemplatesValidationRules; // Validation rule name
path: string; // jsonpath of the field whose value is going to be evaluated.
message: string; // User-friendly string message describing the failure, should the rule not be satisfied
min?: number; // For 'integer' rule
max?: number; // For 'integer' rule
minLength?: number; // For 'string' rule
maxLength?: number; // For 'string' rule
regex?: string; // For 'regex' rule
values?: string[]; // For 'enum' rule
justWarning?: boolean;
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ import {
VIRTUAL_MACHINE_EXISTS,
VIRTUAL_MACHINE_TEMPLATE_EXISTS,
} from '../../../../utils/validations/strings';
import {
getFieldReadableTitle,
getFieldTitle,
getFieldId,
} from '../../utils/vm-settings-tab-utils';
import { concatImmutableLists, iGet } from '../../../../utils/immutable';
import { getFieldReadableTitle, getFieldTitle } from '../../utils/vm-settings-tab-utils';
import {
checkTabValidityChanged,
iGetCommonData,
Expand All @@ -42,6 +46,10 @@ import { validatePositiveInteger } from '../../../../utils/validations/common';
import { pluralize } from '../../../../utils/strings';
import { vmSettingsOrder } from '../initial-state/vm-settings-tab-initial-state';
import { getValidationUpdate } from './utils';
import {
getTemplateValidations,
runValidation,
} from './common-template-validations/common-templates-validations';

const validateVm: VmSettingsValidator = (field, options) => {
const { getState, id } = options;
Expand Down Expand Up @@ -95,6 +103,26 @@ export const validateOperatingSystem: VmSettingsValidator = (field) => {
return asValidationObject(`Select matching for: ${guestFullName}`, ValidationErrorType.Info);
};

const memoryValidation: VmSettingsValidator = (field, options): ValidationObject => {
const memValueGB = iGetFieldValue(field);
if (!memValueGB) {
return null;
}
const memValueBytes = memValueGB * 1024 ** 3;
const validations = getTemplateValidations(options, getFieldId(VMSettingsField.MEMORY));
if (validations.length === 0) {
return null;
}

const validationResult = runValidation(validations, memValueBytes);

if (!validationResult.isValid) {
return asValidationObject(validationResult.errorMsg, ValidationErrorType.Error);
}

return null;
};

const asVMSettingsFieldValidator = (
validator: (value: string, opts: { subject: string }) => ValidationObject,
) => (field) =>
Expand Down Expand Up @@ -135,8 +163,12 @@ const validationConfig: VMSettingsValidationConfig = {
validator: asVMSettingsFieldValidator(validatePositiveInteger),
},
[VMSettingsField.MEMORY]: {
detectValueChanges: [VMSettingsField.MEMORY],
validator: asVMSettingsFieldValidator(validatePositiveInteger),
detectValueChanges: [
VMSettingsField.MEMORY,
VMSettingsField.OPERATING_SYSTEM,
VMSettingsField.WORKLOAD_PROFILE,
],
validator: memoryValidation,
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { getNamespace, getName } from '@console/shared/src/selectors';
import { TemplateKind } from '@console/internal/module/k8s';
import { VirtualMachineModel } from '../../models';
import { VMKind, VMLikeEntityKind } from '../../types';
import { iGetIn } from '../../utils/immutable';
import {
TEMPLATE_FLAVOR_LABEL,
TEMPLATE_OS_LABEL,
TEMPLATE_WORKLOAD_LABEL,
CUSTOM_FLAVOR,
TEMPLATE_TYPE_LABEL,
} from '../../constants';
import { getLabels } from '../selectors';
import { getOperatingSystem, getWorkloadProfile } from '../vm/selectors';
Expand Down Expand Up @@ -132,3 +134,27 @@ export const getFlavors = (vm: VMLikeEntityKind, templates: TemplateKind[]) => {

return sortedFlavors;
};

export const getRelevantTemplates = (
commonTemplates: TemplateKind[],
os: string,
workloadProfile: string,
flavor: string,
) => {
if (!commonTemplates || commonTemplates.length === 0 || !os || !workloadProfile) {
return [];
}
return (commonTemplates || []).filter(
(template) =>
iGetIn(template, ['metadata', 'labels', TEMPLATE_TYPE_LABEL]) === 'base' &&
(!os || iGetIn(template, ['metadata', 'labels', `${TEMPLATE_OS_LABEL}/${os}`])) &&
(!workloadProfile ||
iGetIn(template, [
'metadata',
'labels',
`${TEMPLATE_WORKLOAD_LABEL}/${workloadProfile}`,
])) &&
(flavor === 'Custom' ||
iGetIn(template, ['metadata', 'labels', `${TEMPLATE_FLAVOR_LABEL}/${flavor}`])),
);
};

0 comments on commit 4ac553e

Please sign in to comment.