]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: cluster upgrade start UI 52493/head
authoravanthakkar <avanjohn@gmail.com>
Mon, 17 Jul 2023 18:11:51 +0000 (23:41 +0530)
committeravanthakkar <avanjohn@gmail.com>
Mon, 31 Jul 2023 08:44:07 +0000 (14:14 +0530)
Fixes: https://tracker.ceph.com/issues/61928
Signed-off-by: avanthakkar <avanjohn@gmail.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/upgrade.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/upgrade.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts

index d8bfde368b975feb16696f9c3d8cb95d08c1ed6f..c94244e0682cdd401cc06d352439bc75db7ec166 100644 (file)
@@ -58,6 +58,7 @@ import { ServiceFormComponent } from './services/service-form/service-form.compo
 import { ServicesComponent } from './services/services.component';
 import { TelemetryComponent } from './telemetry/telemetry.component';
 import { UpgradeComponent } from './upgrade/upgrade.component';
+import { UpgradeStartModalComponent } from './upgrade/upgrade-form/upgrade-start-modal.component';
 
 @NgModule({
   imports: [
@@ -118,7 +119,8 @@ import { UpgradeComponent } from './upgrade/upgrade.component';
     PlacementPipe,
     CreateClusterComponent,
     CreateClusterReviewComponent,
-    UpgradeComponent
+    UpgradeComponent,
+    UpgradeStartModalComponent
   ],
   providers: [NgbActiveModal]
 })
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.html
new file mode 100644 (file)
index 0000000..be12716
--- /dev/null
@@ -0,0 +1,49 @@
+<cd-modal [modalRef]="activeModal">
+  <ng-container class="modal-title">
+    <ng-container i18n>Upgrade Cluster</ng-container>&nbsp;
+  </ng-container>
+
+  <ng-container class="modal-content">
+    <form name="upgradeForm"
+          class="form"
+          #formDir="ngForm"
+          [formGroup]="upgradeForm"
+          novalidate>
+      <div class="modal-body">
+        <div class="form-group row">
+          <label class="cd-col-form-label required"
+                 for="availableVersions"
+                 i18n>New Version</label>
+          <div class="cd-col-form-input">
+            <select id="availableVersions"
+                    name="availableVersions"
+                    class="form-select"
+                    formControlName="availableVersions">
+              <option *ngIf="versions === null"
+                      ngValue="null"
+                      i18n>Loading...</option>
+              <option *ngIf="versions !== null && versions.length === 0"
+                      [ngValue]="null"
+                      i18n>-- No version available --</option>
+              <option *ngIf="versions !== null && versions.length > 0"
+                      [ngValue]="null"
+                      i18n>-- Select a version --</option>
+              <option *ngFor="let version of versions"
+                      [value]="version">{{ version }}</option>
+            </select>
+            <span class="invalid-feedback"
+                  *ngIf="upgradeForm.showError('availableVersions', formDir, 'required')"
+                  i18n>This field is required!</span>
+          </div>
+        </div>
+      </div>
+
+      <div class="modal-footer">
+        <cd-form-button-panel *ngIf="versions"
+                              (submitActionEvent)="startUpgrade()"
+                              [form]="upgradeForm"
+                              [submitText]="actionLabels.START_UPGRADE"></cd-form-button-panel>
+      </div>
+    </form>
+  </ng-container>
+</cd-modal>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.spec.ts
new file mode 100644 (file)
index 0000000..cb68a3f
--- /dev/null
@@ -0,0 +1,30 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { UpgradeComponent } from '../upgrade.component';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { UpgradeService } from '~/app/shared/api/upgrade.service';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { SharedModule } from '~/app/shared/shared.module';
+
+describe('UpgradeComponent', () => {
+  let component: UpgradeComponent;
+  let fixture: ComponentFixture<UpgradeComponent>;
+
+  configureTestBed({
+    imports: [HttpClientTestingModule, SharedModule],
+    schemas: [NO_ERRORS_SCHEMA],
+    declarations: [UpgradeComponent],
+    providers: [UpgradeService]
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(UpgradeComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.ts
new file mode 100644 (file)
index 0000000..c601b77
--- /dev/null
@@ -0,0 +1,66 @@
+import { Component, OnInit } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+import { Observable } from 'rxjs';
+
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { Permission } from '~/app/shared/models/permissions';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { UpgradeService } from '~/app/shared/api/upgrade.service';
+import { UpgradeInfoInterface } from '~/app/shared/models/upgrade.interface';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { NotificationService } from '~/app/shared/services/notification.service';
+
+@Component({
+  selector: 'cd-upgrade-start-modal.component',
+  templateUrl: './upgrade-start-modal.component.html',
+  styleUrls: ['./upgrade-start-modal.component.scss']
+})
+export class UpgradeStartModalComponent implements OnInit {
+  permission: Permission;
+  upgradeInfoError$: Observable<any>;
+  upgradeInfo$: Observable<UpgradeInfoInterface>;
+  upgradeForm: CdFormGroup;
+  icons = Icons;
+  versions: string[];
+
+  constructor(
+    public actionLabels: ActionLabelsI18n,
+    private authStorageService: AuthStorageService,
+    public activeModal: NgbActiveModal,
+    private upgradeService: UpgradeService,
+    private notificationService: NotificationService
+  ) {
+    this.permission = this.authStorageService.getPermissions().configOpt;
+  }
+
+  ngOnInit() {
+    this.upgradeForm = new CdFormGroup({
+      availableVersions: new FormControl(null, [Validators.required])
+    });
+  }
+
+  startUpgrade() {
+    this.upgradeService.start(this.upgradeForm.getValue('availableVersions')).subscribe({
+      next: () => {
+        this.notificationService.show(
+          NotificationType.success,
+          $localize`Started upgrading the cluster`
+        );
+      },
+      error: (error) => {
+        this.upgradeForm.setErrors({ cdSubmitButton: true });
+        this.notificationService.show(
+          NotificationType.error,
+          $localize`Failed to start the upgrade`,
+          error
+        );
+      },
+      complete: () => {
+        this.activeModal.close();
+      }
+    });
+  }
+}
index 49dab51cc28c580779d575a658051afc4394c7ef..dd8f66950b3b079f15bf77986ea2c4ca55be926d 100644 (file)
@@ -40,6 +40,7 @@
                     id="upgrade"
                     aria-label="Upgrade now"
                     [disabled]="(healthData.mgr_map | mgrSummary).total <= 1"
+                    (click)="startUpgradeModal()"
                     i18n>Upgrade now</button>
           </div>
         </ng-container>
index d0d3997d6d582509d17c23c9e7d94677b7c9c508..0250fe9b122db44cc202f86b4951ecd450f2b475 100644 (file)
@@ -56,7 +56,7 @@ describe('UpgradeComponent', () => {
   beforeEach(() => {
     fixture = TestBed.createComponent(UpgradeComponent);
     component = fixture.componentInstance;
-    upgradeInfoSpy = spyOn(TestBed.inject(UpgradeService), 'list');
+    upgradeInfoSpy = spyOn(TestBed.inject(UpgradeService), 'list').and.callFake(() => of(null));
     getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth');
     getHealthSpy.and.returnValue(of(healthPayload));
     fixture.detectChanges();
index e45914afd1a1133c57ad3e8547a0698f9eab91d4..b779a213d6031b0f77030a4c179830faa336987e 100644 (file)
@@ -1,13 +1,16 @@
 import { Component, OnInit } from '@angular/core';
 import { Observable, of } from 'rxjs';
-import { catchError, ignoreElements } from 'rxjs/operators';
+import { catchError, ignoreElements, tap } from 'rxjs/operators';
 import { HealthService } from '~/app/shared/api/health.service';
 import { UpgradeService } from '~/app/shared/api/upgrade.service';
 import { Icons } from '~/app/shared/enum/icons.enum';
 import { Permission } from '~/app/shared/models/permissions';
 import { UpgradeInfoInterface } from '~/app/shared/models/upgrade.interface';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
 import { SummaryService } from '~/app/shared/services/summary.service';
+import { ModalService } from '~/app/shared/services/modal.service';
+import { UpgradeStartModalComponent } from './upgrade-form/upgrade-start-modal.component';
 
 @Component({
   selector: 'cd-upgrade',
@@ -21,10 +24,13 @@ export class UpgradeComponent implements OnInit {
   permission: Permission;
   healthData$: Observable<any>;
   fsid$: Observable<any>;
+  modalRef: NgbModalRef;
+  upgradableVersions: string[];
 
   icons = Icons;
 
   constructor(
+    private modalService: ModalService,
     private summaryService: SummaryService,
     private upgradeService: UpgradeService,
     private authStorageService: AuthStorageService,
@@ -38,7 +44,11 @@ export class UpgradeComponent implements OnInit {
       const version = summary.version.replace('ceph version ', '').split('-');
       this.version = version[0];
     });
-    this.upgradeInfo$ = this.upgradeService.list();
+    this.upgradeInfo$ = this.upgradeService
+      .list()
+      .pipe(
+        tap((upgradeInfo: UpgradeInfoInterface) => (this.upgradableVersions = upgradeInfo.versions))
+      );
     this.upgradeInfoError$ = this.upgradeInfo$?.pipe(
       ignoreElements(),
       catchError((error) => of(error))
@@ -46,4 +56,10 @@ export class UpgradeComponent implements OnInit {
     this.healthData$ = this.healthService.getMinimalHealth();
     this.fsid$ = this.healthService.getClusterFsid();
   }
+
+  startUpgradeModal() {
+    this.modalRef = this.modalService.show(UpgradeStartModalComponent, {
+      versions: this.upgradableVersions.sort()
+    });
+  }
 }
index 3bfc2bef3829bbe40a1a548e7797b0ab96dd7c5d..5acd490cfe764ac2fff2aa68698ff5fb4a0c23cb 100644 (file)
@@ -57,4 +57,11 @@ describe('UpgradeService', () => {
       expectedVersions
     );
   });
+
+  it('should start the upgrade', () => {
+    service.start('18.1.0').subscribe();
+    const req = httpTesting.expectOne('api/cluster/upgrade/start');
+    expect(req.request.method).toBe('POST');
+    expect(req.request.body).toEqual({ version: '18.1.0' });
+  });
 });
index c510164148c72fa591996a53ffc3f7f652a19c5f..6e713ae512299ecc8173be1e1539ebff9bf1622c 100644 (file)
@@ -41,4 +41,8 @@ export class UpgradeService extends ApiClient {
     upgradeInfo.versions = upgradableVersions;
     return upgradeInfo;
   }
+
+  start(version: string) {
+    return this.http.post(`${this.baseURL}/start`, { version: version });
+  }
 }
index aec37abad607d52b472ddfcaa551a516b7d6070a..d299f59fefd0e206c3b45d9fcb856dd48082288a 100644 (file)
@@ -140,6 +140,7 @@ export class ActionLabelsI18n {
   EXPORT: string;
   IMPORT: any;
   MIGRATE: string;
+  START_UPGRADE: string;
 
   constructor() {
     /* Create a new item */
@@ -215,6 +216,8 @@ export class ActionLabelsI18n {
     this.REMOVE_SCHEDULING = $localize`Remove Scheduling`;
     this.PROMOTE = $localize`Promote`;
     this.DEMOTE = $localize`Demote`;
+
+    this.START_UPGRADE = $localize`Start Upgrade`;
   }
 }