configureTestBed,
FixtureHelper,
FormHelper,
- i18nProviders
+ i18nProviders,
+ Mocks
} from '../../../../testing/unit-test-helper';
import { CrushRuleService } from '../../../shared/api/crush-rule.service';
import { CrushNode } from '../../../shared/models/crush-node';
let fixtureHelper: FixtureHelper;
let data: { names: string[]; nodes: CrushNode[] };
- // Object contains mock functions
- const mock = {
- node: (
- name: string,
- id: number,
- type: string,
- type_id: number,
- children?: number[],
- device_class?: string
- ): CrushNode => {
- return { name, type, type_id, id, children, device_class };
- },
- rule: (
- name: string,
- root: string,
- failure_domain: string,
- device_class?: string
- ): CrushRuleConfig => ({
- name,
- root,
- failure_domain,
- device_class
- })
- };
-
// Object contains functions to get something
const get = {
nodeByName: (name: string): CrushNode => data.nodes.find((node) => node.name === name),
* ----> ssd-rack
* ------> 2x osd-rack with ssd
*/
- nodes: [
- // Root node
- mock.node('default', -1, 'root', 11, [-2, -3]),
- // SSD host
- mock.node('ssd-host', -2, 'host', 1, [1, 0, 2]),
- mock.node('osd.0', 0, 'osd', 0, undefined, 'ssd'),
- mock.node('osd.1', 1, 'osd', 0, undefined, 'ssd'),
- mock.node('osd.2', 2, 'osd', 0, undefined, 'ssd'),
- // SSD and HDD mixed devices host
- mock.node('mix-host', -3, 'host', 1, [-4, -5]),
- // HDD rack
- mock.node('hdd-rack', -4, 'rack', 3, [3, 4]),
- mock.node('osd2.0', 3, 'osd-rack', 0, undefined, 'hdd'),
- mock.node('osd2.1', 4, 'osd-rack', 0, undefined, 'hdd'),
- // SSD rack
- mock.node('ssd-rack', -5, 'rack', 3, [5, 6]),
- mock.node('osd2.0', 5, 'osd-rack', 0, undefined, 'ssd'),
- mock.node('osd2.1', 6, 'osd-rack', 0, undefined, 'ssd')
- ]
+ nodes: Mocks.getCrushMap()
};
spyOn(crushRuleService, 'getInfo').and.callFake(() => of(data));
fixture.detectChanges();
});
it('creates a rule with only required fields', () => {
- assert.creation(mock.rule('default-rule', 'default', 'osd-rack'));
+ assert.creation(Mocks.getCrushRuleConfig('default-rule', 'default', 'osd-rack'));
});
it('creates a rule with all fields', () => {
assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
- assert.creation(mock.rule('ssd-host-rule', 'ssd-host', 'osd', 'ssd'));
+ assert.creation(Mocks.getCrushRuleConfig('ssd-host-rule', 'ssd-host', 'osd', 'ssd'));
});
});
});
configureTestBed,
FixtureHelper,
FormHelper,
- i18nProviders
+ i18nProviders,
+ Mocks
} from '../../../../testing/unit-test-helper';
import { ErasureCodeProfileService } from '../../../shared/api/erasure-code-profile.service';
-import { CrushNode } from '../../../shared/models/crush-node';
import { ErasureCodeProfile } from '../../../shared/models/erasure-code-profile';
import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
import { PoolModule } from '../pool.module';
let fixtureHelper: FixtureHelper;
let data: {};
- // Object contains mock functions
- const mock = {
- node: (
- name: string,
- id: number,
- type: string,
- type_id: number,
- children?: number[],
- device_class?: string
- ): CrushNode => {
- return { name, type, type_id, id, children, device_class };
- }
- };
-
configureTestBed({
imports: [
HttpClientTestingModule,
* ----> 3x osd with ssd
* --> mix-host
* ----> hdd-rack
- * ------> 2x osd-rack with hdd
+ * ------> 5x osd-rack with hdd
* ----> ssd-rack
- * ------> 2x osd-rack with ssd
+ * ------> 5x osd-rack with ssd
*/
nodes: [
// Root node
- mock.node('default', -1, 'root', 11, [-2, -3]),
+ Mocks.getCrushNode('default', -1, 'root', 11, [-2, -3]),
// SSD host
- mock.node('ssd-host', -2, 'host', 1, [1, 0, 2]),
- mock.node('osd.0', 0, 'osd', 0, undefined, 'ssd'),
- mock.node('osd.1', 1, 'osd', 0, undefined, 'ssd'),
- mock.node('osd.2', 2, 'osd', 0, undefined, 'ssd'),
+ Mocks.getCrushNode('ssd-host', -2, 'host', 1, [1, 0, 2]),
+ Mocks.getCrushNode('osd.0', 0, 'osd', 0, undefined, 'ssd'),
+ Mocks.getCrushNode('osd.1', 1, 'osd', 0, undefined, 'ssd'),
+ Mocks.getCrushNode('osd.2', 2, 'osd', 0, undefined, 'ssd'),
// SSD and HDD mixed devices host
- mock.node('mix-host', -3, 'host', 1, [-4, -5]),
+ Mocks.getCrushNode('mix-host', -3, 'host', 1, [-4, -5]),
// HDD rack
- mock.node('hdd-rack', -4, 'rack', 3, [3, 4, 5, 6, 7]),
- mock.node('osd2.0', 3, 'osd-rack', 0, undefined, 'hdd'),
- mock.node('osd2.1', 4, 'osd-rack', 0, undefined, 'hdd'),
- mock.node('osd2.2', 5, 'osd-rack', 0, undefined, 'hdd'),
- mock.node('osd2.3', 6, 'osd-rack', 0, undefined, 'hdd'),
- mock.node('osd2.4', 7, 'osd-rack', 0, undefined, 'hdd'),
+ Mocks.getCrushNode('hdd-rack', -4, 'rack', 3, [3, 4, 5, 6, 7]),
+ Mocks.getCrushNode('osd2.0', 3, 'osd-rack', 0, undefined, 'hdd'),
+ Mocks.getCrushNode('osd2.1', 4, 'osd-rack', 0, undefined, 'hdd'),
+ Mocks.getCrushNode('osd2.2', 5, 'osd-rack', 0, undefined, 'hdd'),
+ Mocks.getCrushNode('osd2.3', 6, 'osd-rack', 0, undefined, 'hdd'),
+ Mocks.getCrushNode('osd2.4', 7, 'osd-rack', 0, undefined, 'hdd'),
// SSD rack
- mock.node('ssd-rack', -5, 'rack', 3, [8, 9, 10, 11, 12]),
- mock.node('osd3.0', 8, 'osd-rack', 0, undefined, 'ssd'),
- mock.node('osd3.1', 9, 'osd-rack', 0, undefined, 'ssd'),
- mock.node('osd3.2', 10, 'osd-rack', 0, undefined, 'ssd'),
- mock.node('osd3.3', 11, 'osd-rack', 0, undefined, 'ssd'),
- mock.node('osd3.4', 12, 'osd-rack', 0, undefined, 'ssd')
+ Mocks.getCrushNode('ssd-rack', -5, 'rack', 3, [8, 9, 10, 11, 12]),
+ Mocks.getCrushNode('osd3.0', 8, 'osd-rack', 0, undefined, 'ssd'),
+ Mocks.getCrushNode('osd3.1', 9, 'osd-rack', 0, undefined, 'ssd'),
+ Mocks.getCrushNode('osd3.2', 10, 'osd-rack', 0, undefined, 'ssd'),
+ Mocks.getCrushNode('osd3.3', 11, 'osd-rack', 0, undefined, 'ssd'),
+ Mocks.getCrushNode('osd3.4', 12, 'osd-rack', 0, undefined, 'ssd')
]
};
spyOn(ecpService, 'getInfo').and.callFake(() => of(data));
import { FormControl } from '@angular/forms';
-import { configureTestBed } from '../../../testing/unit-test-helper';
+import { configureTestBed, Mocks } from '../../../testing/unit-test-helper';
import { CrushNode } from '../models/crush-node';
-import { CrushRuleConfig } from '../models/crush-rule';
import { CrushNodeSelectionClass } from './crush.node.selection.class';
describe('CrushNodeSelectionService', () => {
- let service: CrushNodeSelectionClass;
+ const nodes = Mocks.getCrushNodes()
+ let service: CrushNodeSelectionClass;
let controls: {
root: FormControl;
failure: FormControl;
device: FormControl;
};
- // Object contains mock functions
- const mock = {
- node: (
- name: string,
- id: number,
- type: string,
- type_id: number,
- children?: number[],
- device_class?: string
- ): CrushNode => {
- return { name, type, type_id, id, children, device_class };
- },
- rule: (
- name: string,
- root: string,
- failure_domain: string,
- device_class?: string
- ): CrushRuleConfig => ({
- name,
- root,
- failure_domain,
- device_class
- }),
- nodes: [] as CrushNode[]
- };
-
- /**
- * Create the following test crush map:
- * > default
- * --> ssd-host
- * ----> 3x osd with ssd
- * --> mix-host
- * ----> hdd-rack
- * ------> 2x osd-rack with hdd
- * ----> ssd-rack
- * ------> 2x osd-rack with ssd
- */
- mock.nodes = [
- // Root node
- mock.node('default', -1, 'root', 11, [-2, -3]),
- // SSD host
- mock.node('ssd-host', -2, 'host', 1, [1, 0, 2]),
- mock.node('osd.0', 0, 'osd', 0, undefined, 'ssd'),
- mock.node('osd.1', 1, 'osd', 0, undefined, 'ssd'),
- mock.node('osd.2', 2, 'osd', 0, undefined, 'ssd'),
- // SSD and HDD mixed devices host
- mock.node('mix-host', -3, 'host', 1, [-4, -5]),
- // HDD rack
- mock.node('hdd-rack', -4, 'rack', 3, [3, 4]),
- mock.node('osd2.0', 3, 'osd-rack', 0, undefined, 'hdd'),
- mock.node('osd2.1', 4, 'osd-rack', 0, undefined, 'hdd'),
- // SSD rack
- mock.node('ssd-rack', -5, 'rack', 3, [5, 6]),
- mock.node('osd2.0', 5, 'osd-rack', 0, undefined, 'ssd'),
- mock.node('osd2.1', 6, 'osd-rack', 0, undefined, 'ssd')
- ];
-
// Object contains functions to get something
const get = {
- nodeByName: (name: string): CrushNode => mock.nodes.find((node) => node.name === name),
+ nodeByName: (name: string): CrushNode => nodes.find((node) => node.name === name),
nodesByNames: (names: string[]): CrushNode[] => names.map(get.nodeByName)
};
// Normally this should be extended by the class using it
service = new CrushNodeSelectionClass();
// Therefore to get it working correctly use "this" instead of "service"
- service.initCrushNodeSelection(mock.nodes, controls.root, controls.failure, controls.device);
+ service.initCrushNodeSelection(nodes, controls.root, controls.failure, controls.device);
});
it('should be created', () => {
expect(service).toBeTruthy();
- expect(mock.nodes.length).toBe(12);
+ expect(nodes.length).toBe(12);
});
describe('lists', () => {
});
it('has the following lists after init', () => {
- assert.failureDomains(mock.nodes, ['host', 'osd', 'osd-rack', 'rack']); // Not root as root only exist once
+ assert.failureDomains(nodes, ['host', 'osd', 'osd-rack', 'rack']); // Not root as root only exist once
expect(service.devices).toEqual(['hdd', 'ssd']);
});
controls.root.setValue(get.nodeByName('mix-host'));
expect(service.devices).toEqual(['hdd', 'ssd']);
assert.failureDomains(
- get.nodesByNames(['hdd-rack', 'ssd-rack', 'osd2.0', 'osd2.1', 'osd2.0', 'osd2.1']),
+ get.nodesByNames(['hdd-rack', 'ssd-rack', 'osd2.0', 'osd2.1', 'osd3.0', 'osd3.1']),
['osd-rack', 'rack']
);
});
import { CdFormGroup } from '../app/shared/forms/cd-form-group';
import { CdTableAction } from '../app/shared/models/cd-table-action';
import { CdTableSelection } from '../app/shared/models/cd-table-selection';
+import { CrushNode } from '../app/shared/models/crush-node';
+import { CrushRule, CrushRuleConfig } from '../app/shared/models/crush-rule';
import { Permission } from '../app/shared/models/permissions';
import {
AlertmanagerAlert,
formHelper.expectErrorChange(fieldName, 'thisPasswordIsWayTooBig', 'pattern');
}
}
+
+export class Mocks {
+ static getCrushNode(
+ name: string,
+ id: number,
+ type: string,
+ type_id: number,
+ children?: number[],
+ device_class?: string
+ ): CrushNode {
+ return { name, type, type_id, id, children, device_class };
+ }
+
+ /**
+ * Create the following test crush map:
+ * > default
+ * --> ssd-host
+ * ----> 3x osd with ssd
+ * --> mix-host
+ * ----> hdd-rack
+ * ------> 2x osd-rack with hdd
+ * ----> ssd-rack
+ * ------> 2x osd-rack with ssd
+ */
+ static getCrushMap(): CrushNode[] {
+ return [
+ // Root node
+ this.getCrushNode('default', -1, 'root', 11, [-2, -3]),
+ // SSD host
+ this.getCrushNode('ssd-host', -2, 'host', 1, [1, 0, 2]),
+ this.getCrushNode('osd.0', 0, 'osd', 0, undefined, 'ssd'),
+ this.getCrushNode('osd.1', 1, 'osd', 0, undefined, 'ssd'),
+ this.getCrushNode('osd.2', 2, 'osd', 0, undefined, 'ssd'),
+ // SSD and HDD mixed devices host
+ this.getCrushNode('mix-host', -3, 'host', 1, [-4, -5]),
+ // HDD rack
+ this.getCrushNode('hdd-rack', -4, 'rack', 3, [3, 4]),
+ this.getCrushNode('osd2.0', 3, 'osd-rack', 0, undefined, 'hdd'),
+ this.getCrushNode('osd2.1', 4, 'osd-rack', 0, undefined, 'hdd'),
+ // SSD rack
+ this.getCrushNode('ssd-rack', -5, 'rack', 3, [5, 6]),
+ this.getCrushNode('osd3.0', 5, 'osd-rack', 0, undefined, 'ssd'),
+ this.getCrushNode('osd3.1', 6, 'osd-rack', 0, undefined, 'ssd')
+ ];
+ }
+
+ /**
+ * Generates an simple crush map with multiple hosts that have OSDs with either ssd or hdd OSDs.
+ * Hosts with zero or even numbers at the end have SSD OSDs the other hosts have hdd OSDs.
+ *
+ * Host names follow the following naming convention:
+ * host.$index
+ * $index represents a number count started at 0 (like an index within an array) (same for OSDs)
+ *
+ * OSD names follow the following naming convention:
+ * osd.$hostIndex.$osdIndex
+ *
+ * The following crush map will be generated with the set defaults:
+ * > default
+ * --> host.0 (has only ssd OSDs)
+ * ----> osd.0.0
+ * ----> osd.0.1
+ * ----> osd.0.2
+ * ----> osd.0.3
+ * --> host.1 (has only hdd OSDs)
+ * ----> osd.1.0
+ * ----> osd.1.1
+ * ----> osd.1.2
+ * ----> osd.1.3
+ */
+ static generateSimpleCrushMap(hosts: number = 2, osds: number = 4): CrushNode[] {
+ const nodes = [];
+ const createOsdLeafs = (hostSuffix: number): number[] => {
+ let osdId = 0;
+ const osdIds = [];
+ const osdsInUse = hostSuffix * osds;
+ for (let o = 0; o < osds; o++) {
+ osdIds.push(osdId);
+ nodes.push(
+ this.getCrushNode(
+ `osd.${hostSuffix}.${osdId}`,
+ osdId + osdsInUse,
+ 'osd',
+ 0,
+ undefined,
+ hostSuffix % 2 === 0 ? 'ssd' : 'hdd'
+ )
+ );
+ osdId++;
+ }
+ return osdIds;
+ };
+ const createHostBuckets = (): number[] => {
+ let hostId = -2;
+ const hostIds = [];
+ for (let h = 0; h < hosts; h++) {
+ const hostSuffix = hostId * -1 - 2;
+ hostIds.push(hostId);
+ nodes.push(
+ this.getCrushNode(`host.${hostSuffix}`, hostId, 'host', 1, createOsdLeafs(hostSuffix))
+ );
+ hostId--;
+ }
+ return hostIds;
+ };
+ nodes.push(this.getCrushNode('default', -1, 'root', 11, createHostBuckets()));
+ return nodes;
+ }
+
+ static getCrushRuleConfig(
+ name: string,
+ root: string,
+ failure_domain: string,
+ device_class?: string
+ ): CrushRuleConfig {
+ return {
+ name,
+ root,
+ failure_domain,
+ device_class
+ };
+ }
+
+ static getCrushRule({
+ id = 0,
+ name = 'somePoolName',
+ min = 1,
+ max = 10,
+ type = 'replicated',
+ failureDomain = 'osd',
+ itemName = 'default' // This string also sets the device type - "default~ssd" <- ssd usage only
+ }: {
+ max?: number;
+ min?: number;
+ id?: number;
+ name?: string;
+ type?: string;
+ failureDomain?: string;
+ itemName?: string;
+ }): CrushRule {
+ const typeNumber = type === 'erasure' ? 3 : 1;
+ const rule = new CrushRule();
+ rule.max_size = max;
+ rule.min_size = min;
+ rule.rule_id = id;
+ rule.ruleset = typeNumber;
+ rule.rule_name = name;
+ rule.steps = [
+ {
+ item_name: itemName,
+ item: -1,
+ op: 'take'
+ },
+ {
+ num: 0,
+ type: failureDomain,
+ op: 'choose_firstn'
+ },
+ {
+ op: 'emit'
+ }
+ ];
+ return rule;
+ }
+}