]> git.proxmox.com Git - ceph.git/blobdiff - 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
index f9a675b48bf483565065722c842caae049226b2f..fd67cdec30c0f888ab7b5e6233654dbdd4e074fb 100644 (file)
@@ -1,93 +1,29 @@
 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);
@@ -101,6 +37,19 @@ describe('CrushNodeSelectionService', () => {
       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]);
+        }
+      });
     }
   };
 
@@ -117,12 +66,12 @@ describe('CrushNodeSelectionService', () => {
     // 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', () => {
@@ -134,23 +83,31 @@ describe('CrushNodeSelectionService', () => {
     });
 
     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']
+      });
     });
   });
 
@@ -204,4 +161,60 @@ describe('CrushNodeSelectionService', () => {
       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']
+        }
+      );
+    });
+  });
 });