]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Cluster Creation Create OSDs Section
authorNizamudeen A <nia@redhat.com>
Tue, 17 Aug 2021 13:34:31 +0000 (19:04 +0530)
committerNizamudeen A <nia@redhat.com>
Wed, 13 Oct 2021 10:26:09 +0000 (15:56 +0530)
Create OSDs section in cluster creation wizard

Fixes: https://tracker.ceph.com/issues/51991
Fixes: https://tracker.ceph.com/issues/52298
Signed-off-by: Nizamudeen A <nia@redhat.com>
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
22 files changed:
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/create-cluster.po.ts
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/02-create-cluster-add-host.e2e-spec.ts
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-create-osds.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-review.e2e-spec.ts
src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/04-cluster-check.e2e-spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/wizard-steps.service.ts

index 22941b28e7cb71a780d1ab1dcfd82dcc2bb27283..4ae03f4aaae293f6e937b438059560ca2b52ac44 100644 (file)
@@ -93,7 +93,10 @@ export class CreateClusterWizardHelper extends PageHelper {
       }
     }
     cy.get('cd-modal cd-submit-button').click();
+    this.checkLabelExists(hostname, labels, add);
+  }
 
+  checkLabelExists(hostname: string, labels: string[], add: boolean) {
     // Verify labels are added or removed from Labels column
     // First find row with hostname, then find labels in the row
     this.getTableCell(this.columnIndex.hostname, hostname)
@@ -110,4 +113,18 @@ export class CreateClusterWizardHelper extends PageHelper {
         }
       });
   }
+
+  createOSD(deviceType: 'hdd' | 'ssd') {
+    // Click Primary devices Add button
+    cy.get('cd-osd-devices-selection-groups[name="Primary"]').as('primaryGroups');
+    cy.get('@primaryGroups').find('button').click();
+
+    // Select all devices with `deviceType`
+    cy.get('cd-osd-devices-selection-modal').within(() => {
+      cy.get('.modal-footer .tc_submitButton').as('addButton').should('be.disabled');
+      this.filterTable('Type', deviceType);
+      this.getTableCount('total').should('be.gte', 1);
+      cy.get('@addButton').click();
+    });
+  }
 }
index 7bf5b5be3d49677cb1d64ac54e81d1a0b1459a44..3b64c2987c9e55f0d25e2daa11b9cf76ec06a34f 100644 (file)
@@ -8,7 +8,7 @@ describe('Create cluster add host page', () => {
     'ceph-node-02.cephlab.com'
   ];
   const addHost = (hostname: string, exist?: boolean) => {
-    createCluster.add(hostname, exist, true);
+    createCluster.add(hostname, exist, false);
     createCluster.checkExist(hostname, true);
   };
 
@@ -25,13 +25,18 @@ describe('Create cluster add host page', () => {
     cy.get('.title').should('contain.text', 'Add Hosts');
   });
 
-  it('should check existing host and add new hosts into maintenance mode', () => {
+  it('should check existing host and add new hosts', () => {
     createCluster.checkExist(hostnames[0], true);
 
     addHost(hostnames[1], false);
     addHost(hostnames[2], false);
   });
 
+  it('should verify "_no_schedule" label is added', () => {
+    createCluster.checkLabelExists(hostnames[1], ['_no_schedule'], true);
+    createCluster.checkLabelExists(hostnames[2], ['_no_schedule'], true);
+  });
+
   it('should not add an existing host', () => {
     createCluster.add(hostnames[0], true);
   });
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-create-osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-create-osds.e2e-spec.ts
new file mode 100644 (file)
index 0000000..92c0739
--- /dev/null
@@ -0,0 +1,38 @@
+import { CreateClusterWizardHelper } from 'cypress/integration/cluster/create-cluster.po';
+import { OSDsPageHelper } from 'cypress/integration/cluster/osds.po';
+
+const osds = new OSDsPageHelper();
+
+describe('Create cluster create osds page', () => {
+  const createCluster = new CreateClusterWizardHelper();
+
+  beforeEach(() => {
+    cy.login();
+    Cypress.Cookies.preserveOnce('token');
+    createCluster.navigateTo();
+    createCluster.createCluster();
+    cy.get('button[aria-label="Next"]').click();
+  });
+
+  it('should check if nav-link and title contains Create OSDs', () => {
+    cy.get('.nav-link').should('contain.text', 'Create OSDs');
+
+    cy.get('.title').should('contain.text', 'Create OSDs');
+  });
+
+  describe('when Orchestrator is available', () => {
+    it('should create OSDs', () => {
+      osds.navigateTo();
+      osds.getTableCount('total').as('initOSDCount');
+
+      createCluster.navigateTo();
+      createCluster.createCluster();
+      cy.get('button[aria-label="Next"]').click();
+
+      createCluster.createOSD('hdd');
+
+      cy.get('button[aria-label="Next"]').click();
+      cy.get('button[aria-label="Next"]').click();
+    });
+  });
+});
index 17dd84994d60e30262f34d76d2eb45a95328af41..624f457458ff22ebf499fee167bdac195177e76d 100644 (file)
@@ -10,13 +10,12 @@ describe('Create Cluster Review page', () => {
     createCluster.createCluster();
 
     cy.get('button[aria-label="Next"]').click();
+    cy.get('button[aria-label="Next"]').click();
   });
 
   describe('navigation link and title test', () => {
     it('should check if nav-link and title contains Review', () => {
       cy.get('.nav-link').should('contain.text', 'Review');
-
-      cy.get('.title').should('contain.text', 'Review');
     });
   });
 
@@ -27,6 +26,7 @@ describe('Create Cluster Review page', () => {
 
       // check for fields in table
       createCluster.getStatusTables().should('contain.text', 'Hosts');
+      createCluster.getStatusTables().should('contain.text', 'Storage Capacity');
     });
 
     it('should check Hosts by Label and Host Details tables are present', () => {
index 9717dd8b7dbf8315bf9d952df14399e557904bb0..116cbd789c8c01c7a270a292a82a4610dfcf4887 100644 (file)
@@ -1,5 +1,6 @@
 import { CreateClusterWizardHelper } from 'cypress/integration/cluster/create-cluster.po';
 import { HostsPageHelper } from 'cypress/integration/cluster/hosts.po';
+import { OSDsPageHelper } from 'cypress/integration/cluster/osds.po';
 
 describe('when cluster creation is completed', () => {
   const createCluster = new CreateClusterWizardHelper();
@@ -13,6 +14,7 @@ describe('when cluster creation is completed', () => {
     createCluster.navigateTo();
     createCluster.createCluster();
 
+    cy.get('button[aria-label="Next"]').click();
     cy.get('button[aria-label="Next"]').click();
     cy.get('button[aria-label="Next"]').click();
 
@@ -26,9 +28,9 @@ describe('when cluster creation is completed', () => {
     beforeEach(() => {
       hosts.navigateTo();
     });
-    it('should have already exited from maintenance', () => {
+    it('should have removed "_no_schedule" label', () => {
       for (let host = 0; host < hostnames.length; host++) {
-        cy.get('datatable-row-wrapper').should('not.have.text', 'maintenance');
+        cy.get('datatable-row-wrapper').should('not.have.text', '_no_schedule');
       }
     });
 
@@ -46,4 +48,16 @@ describe('when cluster creation is completed', () => {
       });
     });
   });
+
+  describe('OSDs page', () => {
+    const osds = new OSDsPageHelper();
+
+    beforeEach(() => {
+      osds.navigateTo();
+    });
+
+    it('should check if osds are created', { retries: 1 }, () => {
+      osds.expectTableCount('total', 2);
+    });
+  });
 });
index fa010fdcd8edea6b1c3067e06b6360b872015dc8..f95dfdb910f8185020bfd451b710b26abb35137c 100644 (file)
@@ -9,6 +9,13 @@
               class="bold">Hosts</td>
           <td>{{ hostsCount }}</td>
         </tr>
+        <tr>
+          <td i18n
+              class="bold">Storage Capacity</td>
+          <td><span i18n
+                    *ngIf="filteredDevices && capacity">Number of devices: {{ filteredDevices.length }}. Raw capacity:
+            {{ capacity | dimlessBinary }}.</span></td>
+        </tr>
       </table>
     </fieldset>
   </div>
index c78e8f910ece618b2b77dac846630ca330d1cae2..3abbcb122865e8b2bd94b19a22f4ab8444263ee5 100644 (file)
@@ -4,6 +4,8 @@ import _ from 'lodash';
 
 import { HostService } from '~/app/shared/api/host.service';
 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
+import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
+import { InventoryDevice } from '../inventory/inventory-devices/inventory-device.model';
 
 @Component({
   selector: 'cd-create-cluster-review',
@@ -18,8 +20,10 @@ export class CreateClusterReviewComponent implements OnInit {
   labelOccurrences = {};
   hostsCountPerLabel: object[] = [];
   uniqueLabels: Set<string> = new Set();
+  filteredDevices: InventoryDevice[] = [];
+  capacity = 0;
 
-  constructor(private hostService: HostService) {}
+  constructor(private hostService: HostService, public wizardStepService: WizardStepsService) {}
 
   ngOnInit() {
     this.hostsDetails = {
@@ -82,5 +86,8 @@ export class CreateClusterReviewComponent implements OnInit {
       this.hostsByLabel['data'] = [...this.hostsCountPerLabel];
       this.hostsDetails['data'] = [...this.hosts];
     });
+
+    this.filteredDevices = this.wizardStepService.osdDevices;
+    this.capacity = this.wizardStepService.osdCapacity;
   }
 }
index 38887328ec3ab75c804aa88aaeeac690b5941d79..d9e8ec43d9cc97a515fda97b375b8f814289ee5b 100644 (file)
         </div>
         <div *ngSwitchCase="'2'"
              class="ml-5">
+          <h4 class="title"
+              i18n>Create OSDs</h4>
+          <br>
+          <div class="alignForm">
+            <cd-osd-form [clusterCreation]="true"></cd-osd-form>
+          </div>
+        </div>
+        <div *ngSwitchCase="'3'"
+             class="ml-5">
           <cd-create-cluster-review></cd-create-cluster-review>
         </div>
       </ng-container>
index b0564703840d47043226df2abe85b6d2d67f04d5..8f9d7328e34ee821725fbd62e50371b47e217877 100644 (file)
@@ -9,6 +9,7 @@ import { CephModule } from '~/app/ceph/ceph.module';
 import { CoreModule } from '~/app/core/core.module';
 import { HostService } from '~/app/shared/api/host.service';
 import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
+import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
 import { AppConstants } from '~/app/shared/constants/app.constants';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
@@ -24,16 +25,19 @@ describe('CreateClusterComponent', () => {
   let modalServiceShowSpy: jasmine.Spy;
   const projectConstants: typeof AppConstants = AppConstants;
 
-  configureTestBed({
-    imports: [
-      HttpClientTestingModule,
-      RouterTestingModule,
-      ToastrModule.forRoot(),
-      SharedModule,
-      CoreModule,
-      CephModule
-    ]
-  });
+  configureTestBed(
+    {
+      imports: [
+        HttpClientTestingModule,
+        RouterTestingModule,
+        ToastrModule.forRoot(),
+        SharedModule,
+        CoreModule,
+        CephModule
+      ]
+    },
+    [LoadingPanelComponent]
+  );
 
   beforeEach(() => {
     fixture = TestBed.createComponent(CreateClusterComponent);
@@ -91,7 +95,7 @@ describe('CreateClusterComponent', () => {
     component.onNextStep();
     fixture.detectChanges();
     expect(wizardStepServiceSpy).toHaveBeenCalledTimes(1);
-    expect(hostServiceSpy).toBeCalledTimes(2);
+    expect(hostServiceSpy).toBeCalledTimes(1);
   });
 
   it('should show the button labels correctly', () => {
@@ -102,6 +106,13 @@ describe('CreateClusterComponent', () => {
     let cancelBtnLabel = component.showCancelButtonLabel();
     expect(cancelBtnLabel).toEqual('Cancel');
 
+    component.onNextStep();
+    fixture.detectChanges();
+    submitBtnLabel = component.showSubmitButtonLabel();
+    expect(submitBtnLabel).toEqual('Next');
+    cancelBtnLabel = component.showCancelButtonLabel();
+    expect(cancelBtnLabel).toEqual('Back');
+
     // Last page of the wizard
     component.onNextStep();
     fixture.detectChanges();
index b47a63e8cec008c1e8ec6166b8711f385d3b98bb..6d78a2110b46179d52f215a39f850223c5701870 100644 (file)
@@ -1,21 +1,26 @@
-import { Component, OnDestroy, TemplateRef, ViewChild } from '@angular/core';
+import { Component, EventEmitter, OnDestroy, Output, TemplateRef, ViewChild } from '@angular/core';
 import { Router } from '@angular/router';
 
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import _ from 'lodash';
 import { forkJoin, Subscription } from 'rxjs';
 import { finalize } from 'rxjs/operators';
 
 import { ClusterService } from '~/app/shared/api/cluster.service';
 import { HostService } from '~/app/shared/api/host.service';
+import { OsdService } from '~/app/shared/api/osd.service';
 import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
-import { ActionLabelsI18n, AppConstants } from '~/app/shared/constants/app.constants';
+import { ActionLabelsI18n, AppConstants, URLVerbs } from '~/app/shared/constants/app.constants';
 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+import { FinishedTask } from '~/app/shared/models/finished-task';
 import { Permissions } from '~/app/shared/models/permissions';
 import { WizardStepModel } from '~/app/shared/models/wizard-steps';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { NotificationService } from '~/app/shared/services/notification.service';
+import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
+import { DriveGroup } from '../osd/osd-form/drive-group.model';
 
 @Component({
   selector: 'cd-create-cluster',
@@ -29,10 +34,15 @@ export class CreateClusterComponent implements OnDestroy {
   currentStepSub: Subscription;
   permissions: Permissions;
   projectConstants: typeof AppConstants = AppConstants;
-  stepTitles = ['Add Hosts', 'Review'];
+  stepTitles = ['Add Hosts', 'Create OSDs', 'Review'];
   startClusterCreation = false;
   observables: any = [];
   modalRef: NgbModalRef;
+  driveGroup = new DriveGroup();
+  driveGroups: Object[] = [];
+
+  @Output()
+  submitAction = new EventEmitter();
 
   constructor(
     private authStorageService: AuthStorageService,
@@ -42,7 +52,10 @@ export class CreateClusterComponent implements OnDestroy {
     private notificationService: NotificationService,
     private actionLabels: ActionLabelsI18n,
     private clusterService: ClusterService,
-    private modalService: ModalService
+    private modalService: ModalService,
+    private taskWrapper: TaskWrapperService,
+    private osdService: OsdService,
+    private wizardStepService: WizardStepsService
   ) {
     this.permissions = this.authStorageService.getPermissions();
     this.currentStepSub = this.stepsService.getCurrentStep().subscribe((step: WizardStepModel) => {
@@ -95,17 +108,42 @@ export class CreateClusterComponent implements OnDestroy {
       .subscribe({
         error: (error) => error.preventDefault()
       });
+
+    this.taskWrapper
+      .wrapTaskAroundCall({
+        task: new FinishedTask('osd/' + URLVerbs.CREATE, {
+          tracking_id: _.join(_.map(this.driveGroups, 'service_id'), ', ')
+        }),
+        call: this.osdService.create(this.driveGroups)
+      })
+      .subscribe({
+        error: (error) => error.preventDefault(),
+        complete: () => {
+          this.submitAction.emit();
+        }
+      });
   }
 
   onNextStep() {
     if (!this.stepsService.isLastStep()) {
       this.hostService.list().subscribe((hosts) => {
         hosts.forEach((host) => {
-          if (host['status'] === 'maintenance') {
-            this.observables.push(this.hostService.update(host['hostname'], false, [], true));
+          const index = host['labels'].indexOf('_no_schedule', 0);
+          if (index > -1) {
+            host['labels'].splice(index, 1);
+            this.observables.push(this.hostService.update(host['hostname'], true, host['labels']));
           }
         });
       });
+      this.driveGroup = this.wizardStepService.sharedData;
+      this.stepsService.getCurrentStep().subscribe((step: WizardStepModel) => {
+        this.currentStep = step;
+      });
+      if (this.currentStep.stepIndex === 2 && this.driveGroup) {
+        const user = this.authStorageService.getUsername();
+        this.driveGroup.setName(`dashboard-${user}-${_.now()}`);
+        this.driveGroups.push(this.driveGroup.spec);
+      }
       this.stepsService.moveToNextStep();
     } else {
       this.onSubmit();
index 2fc8b13b540e6fe0de5928ad369b9cb19bf6dd86..99313a5923aaba331728d801d6616b3a690cba2d 100644 (file)
@@ -25,7 +25,7 @@ export class HostFormComponent extends CdForm implements OnInit {
   hostnames: string[];
   addr: string;
   status: string;
-  allLabels: any;
+  allLabels: string[];
   pageURL: string;
 
   messages = new SelectMessages({
@@ -60,7 +60,6 @@ export class HostFormComponent extends CdForm implements OnInit {
   }
 
   private createForm() {
-    const disableMaintenance = this.pageURL !== 'hosts';
     this.hostForm = new CdFormGroup({
       hostname: new FormControl('', {
         validators: [
@@ -74,7 +73,7 @@ export class HostFormComponent extends CdForm implements OnInit {
         validators: [CdValidators.ip()]
       }),
       labels: new FormControl([]),
-      maintenance: new FormControl({ value: disableMaintenance, disabled: disableMaintenance })
+      maintenance: new FormControl({ value: false, disabled: this.pageURL !== 'hosts' })
     });
   }
 
@@ -83,6 +82,9 @@ export class HostFormComponent extends CdForm implements OnInit {
     this.addr = this.hostForm.get('addr').value;
     this.status = this.hostForm.get('maintenance').value ? 'maintenance' : '';
     this.allLabels = this.hostForm.get('labels').value;
+    if (this.pageURL !== 'hosts' && !this.allLabels.includes('_no_schedule')) {
+      this.allLabels.push('_no_schedule');
+    }
     this.taskWrapper
       .wrapTaskAroundCall({
         task: new FinishedTask('host/' + URLVerbs.ADD, {
index 1a3798b8ff9c0f1a77b30a5213fd48adabea28cd..c70f755f799e84bd2ca0442a462c9e9540d792df 100644 (file)
@@ -115,10 +115,8 @@ export class HostsComponent extends ListWithDetails implements OnInit {
         icon: Icons.enter,
         click: () => this.hostMaintenance(),
         disable: (selection: CdTableSelection) =>
-          this.getDisable('maintenance', selection) ||
-          this.isExecuting ||
-          this.enableButton ||
-          this.clusterCreation
+          this.getDisable('maintenance', selection) || this.isExecuting || this.enableButton,
+        visible: () => !this.clusterCreation
       },
       {
         name: this.actionLabels.EXIT_MAINTENANCE,
@@ -126,10 +124,8 @@ export class HostsComponent extends ListWithDetails implements OnInit {
         icon: Icons.exit,
         click: () => this.hostMaintenance(),
         disable: (selection: CdTableSelection) =>
-          this.getDisable('maintenance', selection) ||
-          this.isExecuting ||
-          !this.enableButton ||
-          this.clusterCreation
+          this.getDisable('maintenance', selection) || this.isExecuting || this.enableButton,
+        visible: () => !this.clusterCreation
       }
     ];
   }
@@ -189,6 +185,13 @@ export class HostsComponent extends ListWithDetails implements OnInit {
     this.orchService.status().subscribe((status: OrchestratorStatus) => {
       this.orchStatus = status;
     });
+
+    if (this.clusterCreation) {
+      const hiddenColumns = ['services', 'ceph_version'];
+      this.columns = this.columns.filter((col: any) => {
+        return !hiddenColumns.includes(col.prop);
+      });
+    }
   }
 
   updateSelection(selection: CdTableSelection) {
index 244bd8162cf4d597eb99884f2bb9fb56ae07677b..54cee708d263407cc9e365d33e444f558a089781 100644 (file)
@@ -4,7 +4,7 @@
           [forceIdentifier]="true"
           [selectionType]="selectionType"
           columnMode="flex"
-          [autoReload]="false"
+          (fetchData)="getDevices()"
           [searchField]="false"
           (updateSelection)="updateSelection($event)"
           (columnFiltersChanged)="onColumnFiltersChanged($event)">
index 5fce0381cd2a5c51c2d3b3ae2c9458a80d79aa0f..29a3ece96d8ae6c695283f5ea3610ef9d368ede9 100644 (file)
@@ -7,6 +7,7 @@ import { RouterTestingModule } from '@angular/router/testing';
 
 import { ToastrModule } from 'ngx-toastr';
 
+import { HostService } from '~/app/shared/api/host.service';
 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
 import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
 import { CdTableAction } from '~/app/shared/models/cd-table-action';
@@ -23,6 +24,7 @@ describe('InventoryDevicesComponent', () => {
   let component: InventoryDevicesComponent;
   let fixture: ComponentFixture<InventoryDevicesComponent>;
   let orchService: OrchestratorService;
+  let hostService: HostService;
 
   const fakeAuthStorageService = {
     getPermissions: () => {
@@ -59,6 +61,7 @@ describe('InventoryDevicesComponent', () => {
   beforeEach(() => {
     fixture = TestBed.createComponent(InventoryDevicesComponent);
     component = fixture.componentInstance;
+    hostService = TestBed.inject(HostService);
     orchService = TestBed.inject(OrchestratorService);
   });
 
@@ -70,6 +73,15 @@ describe('InventoryDevicesComponent', () => {
     expect(component.columns.every((column) => Boolean(column.prop))).toBeTruthy();
   });
 
+  it('should call inventoryDataList only when showOnlyAvailableData is true', () => {
+    const hostServiceSpy = spyOn(hostService, 'inventoryDeviceList').and.callThrough();
+    component.getDevices();
+    expect(hostServiceSpy).toBeCalledTimes(0);
+    component.showAvailDeviceOnly = true;
+    component.getDevices();
+    expect(hostServiceSpy).toBeCalledTimes(1);
+  });
+
   describe('table actions', () => {
     const fakeDevices = require('./fixtures/inventory_list_response.json');
 
index fa778d5b4f29bbb49e0c5195cb728df0c2fbdb40..e0d82cb1975db7e749cdb6c9bfe7bfe020b3fcc5 100644 (file)
@@ -43,6 +43,7 @@ export class InventoryDevicesComponent implements OnInit, OnDestroy {
   // Devices
   @Input() devices: InventoryDevice[] = [];
 
+  @Input() showAvailDeviceOnly = false;
   // Do not display these columns
   @Input() hiddenColumns: string[] = [];
 
@@ -175,6 +176,22 @@ export class InventoryDevicesComponent implements OnInit, OnDestroy {
     }
   }
 
+  getDevices() {
+    if (this.showAvailDeviceOnly) {
+      this.hostService.inventoryDeviceList().subscribe(
+        (devices: InventoryDevice[]) => {
+          this.devices = _.filter(devices, 'available');
+          this.devices = [...this.devices];
+        },
+        () => {
+          this.devices = [];
+        }
+      );
+    } else {
+      this.devices = [...this.devices];
+    }
+  }
+
   ngOnDestroy() {
     if (this.fetchInventorySub) {
       this.fetchInventorySub.unsubscribe();
index da24403dee1e11895eb81de93385a1c6e8eaf0c8..dd60f7959fd9d1eac78772a5812ecd044302dcc0 100644 (file)
@@ -52,17 +52,16 @@ describe('InventoryComponent', () => {
   describe('after ngOnInit', () => {
     it('should load devices', () => {
       fixture.detectChanges();
-      expect(hostService.inventoryDeviceList).toHaveBeenNthCalledWith(1, undefined, false);
       component.refresh(); // click refresh button
-      expect(hostService.inventoryDeviceList).toHaveBeenNthCalledWith(2, undefined, true);
+      expect(hostService.inventoryDeviceList).toHaveBeenNthCalledWith(1, undefined, false);
 
       const newHost = 'host0';
       component.hostname = newHost;
       fixture.detectChanges();
       component.ngOnChanges();
-      expect(hostService.inventoryDeviceList).toHaveBeenNthCalledWith(3, newHost, false);
+      expect(hostService.inventoryDeviceList).toHaveBeenNthCalledWith(2, newHost, false);
       component.refresh(); // click refresh button
-      expect(hostService.inventoryDeviceList).toHaveBeenNthCalledWith(4, newHost, true);
+      expect(hostService.inventoryDeviceList).toHaveBeenNthCalledWith(3, newHost, true);
     });
   });
 });
index 30effc21b53e2baa84ec6dd6ed2309a2ed80b69d..3e53d5c410cd2ceef6a12e5130883f1ea12804bf 100644 (file)
@@ -20,6 +20,7 @@
         <cd-inventory-devices #inventoryDevices
                               [devices]="devices"
                               [filterColumns]="filterColumns"
+                              [showAvailDeviceOnly]="true"
                               [hiddenColumns]="['available', 'osd_ids']"
                               (filterChange)="onFilterChange($event)">
         </cd-inventory-devices>
index fe77a4fe4d8a04d3305d47f110b214891150dcf8..1909803dc3380fb3aeca5361ccd977b281c2a809 100644 (file)
@@ -11,6 +11,7 @@ import { Icons } from '~/app/shared/enum/icons.enum';
 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdTableColumnFiltersChange } from '~/app/shared/models/cd-table-column-filters-change';
+import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
 
 @Component({
   selector: 'cd-osd-devices-selection-modal',
@@ -42,7 +43,8 @@ export class OsdDevicesSelectionModalComponent implements AfterViewInit {
   constructor(
     private formBuilder: CdFormBuilder,
     public activeModal: NgbActiveModal,
-    public actionLabels: ActionLabelsI18n
+    public actionLabels: ActionLabelsI18n,
+    public wizardStepService: WizardStepsService
   ) {
     this.action = actionLabels.ADD;
     this.createForm();
@@ -80,6 +82,8 @@ export class OsdDevicesSelectionModalComponent implements AfterViewInit {
       this.filteredDevices = event.data;
       this.capacity = _.sumBy(this.filteredDevices, 'sys_api.size');
       this.event = event;
+      this.wizardStepService.osdDevices = this.filteredDevices;
+      this.wizardStepService.osdCapacity = this.capacity;
     }
   }
 
index 390f673bdaf5b0e40ca301d7399030eaab164739..675e20fcf59a62f8b8e4b1f3903b816058b85c7c 100644 (file)
@@ -7,8 +7,9 @@
         [formGroup]="form"
         novalidate>
     <div class="card">
-      <div i18n="form title"
-           class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
+      <div i18n="form title|Example: Create Pool@@formTitle"
+           class="card-header"
+           *ngIf="!clusterCreation">{{ action | titlecase }} {{ resource | upperFirst }}</div>
       <div class="card-body">
         <fieldset>
           <cd-osd-devices-selection-groups #dataDeviceSelectionGroups
           </div>
         </fieldset>
       </div>
-      <div class="card-footer">
+      <div class="card-footer"
+           *ngIf="!clusterCreation">
         <cd-form-button-panel #previewButtonPanel
                               (submitActionEvent)="submit()"
                               [form]="form"
index 7b6c44742a75684c5eaabc7252341bfe64bf68ce..411e0f16c88740beb7076633d479f3eaebd83c6b 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnInit, ViewChild } from '@angular/core';
+import { Component, Input, OnInit, ViewChild } from '@angular/core';
 import { FormControl, Validators } from '@angular/forms';
 import { Router } from '@angular/router';
 
@@ -15,6 +15,7 @@ import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { ModalService } from '~/app/shared/services/modal.service';
+import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
 import { OsdCreationPreviewModalComponent } from '../osd-creation-preview-modal/osd-creation-preview-modal.component';
 import { DevicesSelectionChangeEvent } from '../osd-devices-selection-groups/devices-selection-change-event.interface';
 import { DevicesSelectionClearEvent } from '../osd-devices-selection-groups/devices-selection-clear-event.interface';
@@ -61,6 +62,8 @@ export class OsdFormComponent extends CdForm implements OnInit {
   featureList: OsdFeature[] = [];
 
   hasOrchestrator = true;
+  @Input()
+  clusterCreation = false;
 
   constructor(
     public actionLabels: ActionLabelsI18n,
@@ -68,7 +71,8 @@ export class OsdFormComponent extends CdForm implements OnInit {
     private orchService: OrchestratorService,
     private hostService: HostService,
     private router: Router,
-    private modalService: ModalService
+    private modalService: ModalService,
+    public wizardStepService: WizardStepsService
   ) {
     super();
     this.resource = $localize`OSDs`;
@@ -182,6 +186,9 @@ export class OsdFormComponent extends CdForm implements OnInit {
       this.enableFeatures();
     }
     this.driveGroup.setDeviceSelection(event.type, event.filters);
+    if (this.clusterCreation) {
+      this.wizardStepService.sharedData = this.driveGroup;
+    }
   }
 
   onDevicesCleared(event: DevicesSelectionClearEvent) {
index e0fb2be944de02195147569f7e4ffa6478dcd4d7..1c8114033c15e840bbebae811f7ba78246d7b73c 100644 (file)
@@ -2,6 +2,8 @@ import { Injectable } from '@angular/core';
 
 import { BehaviorSubject, Observable } from 'rxjs';
 
+import { InventoryDevice } from '~/app/ceph/cluster/inventory/inventory-devices/inventory-device.model';
+import { DriveGroup } from '~/app/ceph/cluster/osd/osd-form/drive-group.model';
 import { WizardStepModel } from '~/app/shared/models/wizard-steps';
 
 const initialStep = [{ stepIndex: 1, isComplete: false }];
@@ -12,6 +14,9 @@ const initialStep = [{ stepIndex: 1, isComplete: false }];
 export class WizardStepsService {
   steps$: BehaviorSubject<WizardStepModel[]>;
   currentStep$: BehaviorSubject<WizardStepModel> = new BehaviorSubject<WizardStepModel>(null);
+  sharedData = new DriveGroup();
+  osdDevices: InventoryDevice[] = [];
+  osdCapacity = 0;
 
   constructor() {
     this.steps$ = new BehaviorSubject<WizardStepModel[]>(initialStep);