]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Provide shareable mocks in unit test helper
authorStephan Müller <smueller@suse.com>
Wed, 6 May 2020 08:08:41 +0000 (10:08 +0200)
committerStephan Müller <smueller@suse.com>
Tue, 2 Jun 2020 08:38:05 +0000 (10:38 +0200)
Provides a view first mocks:

* Crush map (static and dynamic)
* Crush node
* Crush rule
* Crush rule config

Fixes: https://tracker.ceph.com/issues/44620
Signed-off-by: Stephan Müller <smueller@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/crush-rule-form-modal/crush-rule-form-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/classes/crush.node.selection.class.spec.ts
src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts

index 003ec29bef7b200905e59f558ad471312e2784ac..ca2f744ff9bd8fe8d24fbdb210b2115fb4b2f311 100644 (file)
@@ -11,7 +11,8 @@ import {
   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';
@@ -28,31 +29,6 @@ describe('CrushRuleFormComponent', () => {
   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),
@@ -125,25 +101,7 @@ describe('CrushRuleFormComponent', () => {
        * ----> 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();
@@ -254,12 +212,12 @@ describe('CrushRuleFormComponent', () => {
     });
 
     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'));
     });
   });
 });
index 2628f1f69a42899de41031afac3256fbd7d7a52d..d404bc63d2a318b5b3c2f1f57873b87a8b7812ea 100644 (file)
@@ -12,10 +12,10 @@ import {
   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';
@@ -29,20 +29,6 @@ describe('ErasureCodeProfileFormModalComponent', () => {
   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,
@@ -70,34 +56,34 @@ describe('ErasureCodeProfileFormModalComponent', () => {
        * ----> 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));
index f9a675b48bf483565065722c842caae049226b2f..59a26c5bd3601e6b69fc7542f692ed7835476330 100644 (file)
@@ -1,79 +1,22 @@
 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)
   };
 
@@ -117,12 +60,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,7 +77,7 @@ 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.failureDomains(nodes, ['host', 'osd', 'osd-rack', 'rack']); // Not root as root only exist once
       expect(service.devices).toEqual(['hdd', 'ssd']);
     });
 
@@ -148,7 +91,7 @@ describe('CrushNodeSelectionService', () => {
       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']
       );
     });
index fa8a326bbbe7dd5eb683c96f7f34df93fe3ab365..81047f906377875b702311ffde261c3a505d78a8 100644 (file)
@@ -12,6 +12,8 @@ import { Icons } from '../app/shared/enum/icons.enum';
 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,
@@ -375,3 +377,168 @@ export class IscsiHelper {
     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;
+  }
+}