]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/classes/crush.node.selection.class.spec.ts
import 15.2.5
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / shared / classes / crush.node.selection.class.spec.ts
1 import { FormControl } from '@angular/forms';
2
3 import * as _ from 'lodash';
4
5 import { configureTestBed, Mocks } from '../../../testing/unit-test-helper';
6 import { CrushNode } from '../models/crush-node';
7 import { CrushNodeSelectionClass } from './crush.node.selection.class';
8
9 describe('CrushNodeSelectionService', () => {
10 const nodes = Mocks.getCrushMap();
11
12 let service: CrushNodeSelectionClass;
13 let controls: {
14 root: FormControl;
15 failure: FormControl;
16 device: FormControl;
17 };
18
19 // Object contains functions to get something
20 const get = {
21 nodeByName: (name: string): CrushNode => nodes.find((node) => node.name === name),
22 nodesByNames: (names: string[]): CrushNode[] => names.map(get.nodeByName)
23 };
24
25 // Expects that are used frequently
26 const assert = {
27 formFieldValues: (root: CrushNode, failureDomain: string, device: string) => {
28 expect(controls.root.value).toEqual(root);
29 expect(controls.failure.value).toBe(failureDomain);
30 expect(controls.device.value).toBe(device);
31 },
32 valuesOnRootChange: (
33 rootName: string,
34 expectedFailureDomain: string,
35 expectedDevice: string
36 ) => {
37 const node = get.nodeByName(rootName);
38 controls.root.setValue(node);
39 assert.formFieldValues(node, expectedFailureDomain, expectedDevice);
40 },
41 failureDomainNodes: (
42 failureDomains: { [failureDomain: string]: CrushNode[] },
43 expected: { [failureDomains: string]: string[] | CrushNode[] }
44 ) => {
45 expect(Object.keys(failureDomains)).toEqual(Object.keys(expected));
46 Object.keys(failureDomains).forEach((key) => {
47 if (_.isString(expected[key][0])) {
48 expect(failureDomains[key]).toEqual(get.nodesByNames(expected[key] as string[]));
49 } else {
50 expect(failureDomains[key]).toEqual(expected[key]);
51 }
52 });
53 }
54 };
55
56 configureTestBed({
57 providers: [CrushNodeSelectionClass]
58 });
59
60 beforeEach(() => {
61 controls = {
62 root: new FormControl(null),
63 failure: new FormControl(''),
64 device: new FormControl('')
65 };
66 // Normally this should be extended by the class using it
67 service = new CrushNodeSelectionClass();
68 // Therefore to get it working correctly use "this" instead of "service"
69 service.initCrushNodeSelection(nodes, controls.root, controls.failure, controls.device);
70 });
71
72 it('should be created', () => {
73 expect(service).toBeTruthy();
74 expect(nodes.length).toBe(12);
75 });
76
77 describe('lists', () => {
78 afterEach(() => {
79 // The available buckets should not change
80 expect(service.buckets).toEqual(
81 get.nodesByNames(['default', 'hdd-rack', 'mix-host', 'ssd-host', 'ssd-rack'])
82 );
83 });
84
85 it('has the following lists after init', () => {
86 assert.failureDomainNodes(service.failureDomains, {
87 host: ['ssd-host', 'mix-host'],
88 osd: ['osd.1', 'osd.0', 'osd.2'],
89 rack: ['hdd-rack', 'ssd-rack'],
90 'osd-rack': ['osd2.0', 'osd2.1', 'osd3.0', 'osd3.1']
91 });
92 expect(service.devices).toEqual(['hdd', 'ssd']);
93 });
94
95 it('has the following lists after selection of ssd-host', () => {
96 controls.root.setValue(get.nodeByName('ssd-host'));
97 assert.failureDomainNodes(service.failureDomains, {
98 // Not host as it only exist once
99 osd: ['osd.1', 'osd.0', 'osd.2']
100 });
101 expect(service.devices).toEqual(['ssd']);
102 });
103
104 it('has the following lists after selection of mix-host', () => {
105 controls.root.setValue(get.nodeByName('mix-host'));
106 expect(service.devices).toEqual(['hdd', 'ssd']);
107 assert.failureDomainNodes(service.failureDomains, {
108 rack: ['hdd-rack', 'ssd-rack'],
109 'osd-rack': ['osd2.0', 'osd2.1', 'osd3.0', 'osd3.1']
110 });
111 });
112 });
113
114 describe('selection', () => {
115 it('selects the first root after init automatically', () => {
116 assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
117 });
118
119 it('should select all values automatically by selecting "ssd-host" as root', () => {
120 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
121 });
122
123 it('selects automatically the most common failure domain', () => {
124 // Select mix-host as mix-host has multiple failure domains (osd-rack and rack)
125 assert.valuesOnRootChange('mix-host', 'osd-rack', '');
126 });
127
128 it('should override automatic selections', () => {
129 assert.formFieldValues(get.nodeByName('default'), 'osd-rack', '');
130 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
131 assert.valuesOnRootChange('mix-host', 'osd-rack', '');
132 });
133
134 it('should not override manual selections if possible', () => {
135 controls.failure.setValue('rack');
136 controls.failure.markAsDirty();
137 controls.device.setValue('ssd');
138 controls.device.markAsDirty();
139 assert.valuesOnRootChange('mix-host', 'rack', 'ssd');
140 });
141
142 it('should preselect device by domain selection', () => {
143 controls.failure.setValue('osd');
144 assert.formFieldValues(get.nodeByName('default'), 'osd', 'ssd');
145 });
146 });
147
148 describe('get available OSDs count', () => {
149 it('should have 4 available OSDs with the default selection', () => {
150 expect(service.deviceCount).toBe(4);
151 });
152
153 it('should reduce available OSDs to 2 if a device type is set', () => {
154 controls.device.setValue('ssd');
155 controls.device.markAsDirty();
156 expect(service.deviceCount).toBe(2);
157 });
158
159 it('should show 3 OSDs when selecting "ssd-host"', () => {
160 assert.valuesOnRootChange('ssd-host', 'osd', 'ssd');
161 expect(service.deviceCount).toBe(3);
162 });
163 });
164
165 describe('search tree', () => {
166 it('returns the following list after searching for mix-host', () => {
167 const subNodes = CrushNodeSelectionClass.search(nodes, 'mix-host');
168 expect(subNodes).toEqual(
169 get.nodesByNames([
170 'mix-host',
171 'hdd-rack',
172 'osd2.0',
173 'osd2.1',
174 'ssd-rack',
175 'osd3.0',
176 'osd3.1'
177 ])
178 );
179 });
180
181 it('returns the following list after searching for mix-host with SSDs', () => {
182 const subNodes = CrushNodeSelectionClass.search(nodes, 'mix-host~ssd');
183 expect(subNodes.map((n) => n.name)).toEqual(['mix-host', 'ssd-rack', 'osd3.0', 'osd3.1']);
184 });
185
186 it('returns an empty array if node can not be found', () => {
187 expect(CrushNodeSelectionClass.search(nodes, 'not-there')).toEqual([]);
188 });
189
190 it('returns the following list after searching for mix-host failure domains', () => {
191 const subNodes = CrushNodeSelectionClass.search(nodes, 'mix-host');
192 assert.failureDomainNodes(CrushNodeSelectionClass.getFailureDomains(subNodes), {
193 host: ['mix-host'],
194 rack: ['hdd-rack', 'ssd-rack'],
195 'osd-rack': ['osd2.0', 'osd2.1', 'osd3.0', 'osd3.1']
196 });
197 });
198
199 it('returns the following list after searching for mix-host failure domains for a specific type', () => {
200 const subNodes = CrushNodeSelectionClass.search(nodes, 'mix-host~hdd');
201 const hddHost = _.cloneDeep(get.nodesByNames(['mix-host'])[0]);
202 hddHost.children = [-4];
203 assert.failureDomainNodes(CrushNodeSelectionClass.getFailureDomains(subNodes), {
204 host: [hddHost],
205 rack: ['hdd-rack'],
206 'osd-rack': ['osd2.0', 'osd2.1']
207 });
208 const ssdHost = _.cloneDeep(get.nodesByNames(['mix-host'])[0]);
209 ssdHost.children = [-5];
210 assert.failureDomainNodes(
211 CrushNodeSelectionClass.searchFailureDomains(nodes, 'mix-host~ssd'),
212 {
213 host: [ssdHost],
214 rack: ['ssd-rack'],
215 'osd-rack': ['osd3.0', 'osd3.1']
216 }
217 );
218 });
219 });
220 });