import { FormControl } from '@angular/forms';
-import { configureTestBed } from '../../../testing/unit-test-helper';
+import * as _ from 'lodash';
+
+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.getCrushMap();
+ 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)
};
// Expects that are used frequently
const assert = {
- failureDomains: (nodes: CrushNode[], types: string[]) => {
- const expectation = {};
- types.forEach((type) => (expectation[type] = nodes.filter((node) => node.type === type)));
- const keys = service.failureDomainKeys;
- expect(keys).toEqual(types);
- keys.forEach((key) => {
- expect(service.failureDomains[key].length).toBe(expectation[key].length);
- });
- },
formFieldValues: (root: CrushNode, failureDomain: string, device: string) => {
expect(controls.root.value).toEqual(root);
expect(controls.failure.value).toBe(failureDomain);
const node = get.nodeByName(rootName);
controls.root.setValue(node);
assert.formFieldValues(node, expectedFailureDomain, expectedDevice);
+ },
+ failureDomainNodes: (
+ failureDomains: { [failureDomain: string]: CrushNode[] },
+ expected: { [failureDomains: string]: string[] | CrushNode[] }
+ ) => {
+ expect(Object.keys(failureDomains)).toEqual(Object.keys(expected));
+ Object.keys(failureDomains).forEach((key) => {
+ if (_.isString(expected[key][0])) {
+ expect(failureDomains[key]).toEqual(get.nodesByNames(expected[key] as string[]));
+ } else {
+ expect(failureDomains[key]).toEqual(expected[key]);
+ }
+ });
}
};
// 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.failureDomainNodes(service.failureDomains, {
+ host: ['ssd-host', 'mix-host'],
+ osd: ['osd.1', 'osd.0', 'osd.2'],
+ rack: ['hdd-rack', 'ssd-rack'],
+ 'osd-rack': ['osd2.0', 'osd2.1', 'osd3.0', 'osd3.1']
+ });
expect(service.devices).toEqual(['hdd', 'ssd']);
});
it('has the following lists after selection of ssd-host', () => {
controls.root.setValue(get.nodeByName('ssd-host'));
- assert.failureDomains(get.nodesByNames(['osd.0', 'osd.1', 'osd.2']), ['osd']); // Not host as it only exist once
+ assert.failureDomainNodes(service.failureDomains, {
+ // Not host as it only exist once
+ osd: ['osd.1', 'osd.0', 'osd.2']
+ });
expect(service.devices).toEqual(['ssd']);
});
it('has the following lists after selection of mix-host', () => {
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']),
- ['osd-rack', 'rack']
- );
+ assert.failureDomainNodes(service.failureDomains, {
+ rack: ['hdd-rack', 'ssd-rack'],
+ 'osd-rack': ['osd2.0', 'osd2.1', 'osd3.0', 'osd3.1']
+ });
});
});
expect(service.deviceCount).toBe(3);
});
});
+
+ describe('search tree', () => {
+ it('returns the following list after searching for mix-host', () => {
+ const subNodes = CrushNodeSelectionClass.search(nodes, 'mix-host');
+ expect(subNodes).toEqual(
+ get.nodesByNames([
+ 'mix-host',
+ 'hdd-rack',
+ 'osd2.0',
+ 'osd2.1',
+ 'ssd-rack',
+ 'osd3.0',
+ 'osd3.1'
+ ])
+ );
+ });
+
+ it('returns the following list after searching for mix-host with SSDs', () => {
+ const subNodes = CrushNodeSelectionClass.search(nodes, 'mix-host~ssd');
+ expect(subNodes.map((n) => n.name)).toEqual(['mix-host', 'ssd-rack', 'osd3.0', 'osd3.1']);
+ });
+
+ it('returns an empty array if node can not be found', () => {
+ expect(CrushNodeSelectionClass.search(nodes, 'not-there')).toEqual([]);
+ });
+
+ it('returns the following list after searching for mix-host failure domains', () => {
+ const subNodes = CrushNodeSelectionClass.search(nodes, 'mix-host');
+ assert.failureDomainNodes(CrushNodeSelectionClass.getFailureDomains(subNodes), {
+ host: ['mix-host'],
+ rack: ['hdd-rack', 'ssd-rack'],
+ 'osd-rack': ['osd2.0', 'osd2.1', 'osd3.0', 'osd3.1']
+ });
+ });
+
+ it('returns the following list after searching for mix-host failure domains for a specific type', () => {
+ const subNodes = CrushNodeSelectionClass.search(nodes, 'mix-host~hdd');
+ const hddHost = _.cloneDeep(get.nodesByNames(['mix-host'])[0]);
+ hddHost.children = [-4];
+ assert.failureDomainNodes(CrushNodeSelectionClass.getFailureDomains(subNodes), {
+ host: [hddHost],
+ rack: ['hdd-rack'],
+ 'osd-rack': ['osd2.0', 'osd2.1']
+ });
+ const ssdHost = _.cloneDeep(get.nodesByNames(['mix-host'])[0]);
+ ssdHost.children = [-5];
+ assert.failureDomainNodes(
+ CrushNodeSelectionClass.searchFailureDomains(nodes, 'mix-host~ssd'),
+ {
+ host: [ssdHost],
+ rack: ['ssd-rack'],
+ 'osd-rack': ['osd3.0', 'osd3.1']
+ }
+ );
+ });
+ });
});