From ad1535eea465f5e9f096600e01df1e95813ebb18 Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Tue, 17 Aug 2021 19:04:31 +0530 Subject: [PATCH] mgr/dashboard: Cluster Creation Create OSDs Section 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 Signed-off-by: Aashish Sharma --- .../integration/cluster/create-cluster.po.ts | 17 +++++++ .../02-create-cluster-add-host.e2e-spec.ts | 9 +++- .../03-create-cluster-create-osds.e2e-spec.ts | 38 ++++++++++++++ .../03-create-cluster-review.e2e-spec.ts | 4 +- .../workflow/04-cluster-check.e2e-spec.ts | 18 ++++++- .../create-cluster-review.component.html | 7 +++ .../create-cluster-review.component.ts | 9 +++- .../create-cluster.component.html | 9 ++++ .../create-cluster.component.scss | 4 ++ .../create-cluster.component.spec.ts | 33 ++++++++---- .../create-cluster.component.ts | 50 ++++++++++++++++--- .../hosts/host-form/host-form.component.ts | 8 +-- .../app/ceph/cluster/hosts/hosts.component.ts | 19 ++++--- .../inventory-devices.component.html | 2 +- .../inventory-devices.component.spec.ts | 12 +++++ .../inventory-devices.component.ts | 17 +++++++ .../inventory/inventory.component.spec.ts | 7 ++- ...osd-devices-selection-modal.component.html | 1 + .../osd-devices-selection-modal.component.ts | 6 ++- .../osd/osd-form/osd-form.component.html | 8 +-- .../osd/osd-form/osd-form.component.ts | 11 +++- .../shared/services/wizard-steps.service.ts | 5 ++ 22 files changed, 248 insertions(+), 46 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-create-osds.e2e-spec.ts diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/create-cluster.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/create-cluster.po.ts index 22941b28e7cb7..4ae03f4aaae29 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/create-cluster.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/create-cluster.po.ts @@ -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(); + }); + } } diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/02-create-cluster-add-host.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/02-create-cluster-add-host.e2e-spec.ts index 7bf5b5be3d496..3b64c2987c9e5 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/02-create-cluster-add-host.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/02-create-cluster-add-host.e2e-spec.ts @@ -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 index 0000000000000..92c0739c5ede4 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-create-osds.e2e-spec.ts @@ -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(); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-review.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-review.e2e-spec.ts index 17dd84994d60e..624f457458ff2 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-review.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/03-create-cluster-review.e2e-spec.ts @@ -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', () => { diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/04-cluster-check.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/04-cluster-check.e2e-spec.ts index 9717dd8b7dbf8..116cbd789c8c0 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/04-cluster-check.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/workflow/04-cluster-check.e2e-spec.ts @@ -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); + }); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.html index fa010fdcd8ede..f95dfdb910f81 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.html @@ -9,6 +9,13 @@ class="bold">Hosts {{ hostsCount }} + + Storage Capacity + Number of devices: {{ filteredDevices.length }}. Raw capacity: + {{ capacity | dimlessBinary }}. + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.ts index c78e8f910ece6..3abbcb122865e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster-review.component.ts @@ -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 = 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; } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html index 38887328ec3ab..d9e8ec43d9cc9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.html @@ -44,6 +44,15 @@
+

Create OSDs

+
+
+ +
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.scss index 580c5219e9f2c..a2e88899a66e2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.scss @@ -24,3 +24,7 @@ cd-hosts { display: none; } } + +.alignForm { + margin-left: -1%; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts index b0564703840d4..8f9d7328e34ee 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.spec.ts @@ -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(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts index b47a63e8cec00..6d78a2110b461 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/create-cluster/create-cluster.component.ts @@ -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(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts index 2fc8b13b540e6..99313a5923aab 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts @@ -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, { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts index 1a3798b8ff9c0..c70f755f799e8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.ts @@ -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) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.html index 244bd8162cf4d..54cee708d2634 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.html @@ -4,7 +4,7 @@ [forceIdentifier]="true" [selectionType]="selectionType" columnMode="flex" - [autoReload]="false" + (fetchData)="getDevices()" [searchField]="false" (updateSelection)="updateSelection($event)" (columnFiltersChanged)="onColumnFiltersChanged($event)"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.spec.ts index 5fce0381cd2a5..29a3ece96d8ae 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.spec.ts @@ -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; 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'); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.ts index fa778d5b4f29b..e0d82cb1975db 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.ts @@ -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(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.spec.ts index da24403dee1e1..dd60f7959fd9d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory.component.spec.ts @@ -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); }); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.html index 30effc21b53e2..3e53d5c410cd2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.html @@ -20,6 +20,7 @@ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.ts index fe77a4fe4d8a0..1909803dc3380 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-modal/osd-devices-selection-modal.component.ts @@ -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; } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.html index 390f673bdaf5b..675e20fcf59a6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.html @@ -7,8 +7,9 @@ [formGroup]="form" novalidate>
-
{{ action | titlecase }} {{ resource | upperFirst }}
+
{{ action | titlecase }} {{ resource | upperFirst }}
-