]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Display users current bucket quota usage 38024/head
authorAvan Thakkar <athakkar@redhat.com>
Fri, 17 Apr 2020 08:51:48 +0000 (14:21 +0530)
committerAvan Thakkar <athakkar@redhat.com>
Fri, 20 Nov 2020 06:45:46 +0000 (12:15 +0530)
Fixes: https://tracker.ceph.com/issues/45011
Signed-off-by: Avan Thakkar <athakkar@redhat.com>
(cherry picked from commit 4fabba0bb772d480dcddc83272c83e7714726fc1)

 Conflicts:
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.ts
   - Resolved conflicts due to variable name change and few other import conflicts.

src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-detail/cephfs-detail.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.ts

index f8a5217f9746b477831d8d8602c89ce65bae213c..f82a0005de498caca51681c932e56836f863bfac 100644 (file)
@@ -54,8 +54,8 @@
 <!-- templates -->
 <ng-template #poolUsageTpl
              let-row="row">
-  <cd-usage-bar [totalBytes]="row.size"
-                [usedBytes]="row.used"></cd-usage-bar>
+  <cd-usage-bar [total]="row.size"
+                [used]="row.used"></cd-usage-bar>
 </ng-template>
 
 <ng-template #activityTmpl
index 54182338efd8554a01e5a5034ae620b62df2e4b6..dfa9c4459e5a83514423046b4435c2b016e272af 100644 (file)
@@ -62,8 +62,8 @@
 
     <ng-template #osdUsageTpl
                  let-row="row">
-      <cd-usage-bar [totalBytes]="row.stats.stat_bytes"
-                    [usedBytes]="row.stats.stat_bytes_used">
+      <cd-usage-bar [total]="row.stats.stat_bytes"
+                    [used]="row.stats.stat_bytes_used">
       </cd-usage-bar>
     </ng-template>
   </tab>
index 0a83df29efc196e75ff744267e43d01b730ffb43..560cfba014bed8c11fb85d34bca4019b25b5fc69 100644 (file)
@@ -24,8 +24,8 @@
     <ng-template #poolUsageTpl
                  let-row="row">
       <cd-usage-bar *ngIf="row.stats?.avail_raw?.latest"
-                    [totalBytes]="row.stats.bytes_used.latest + row.stats.avail_raw.latest"
-                    [usedBytes]="row.stats.bytes_used.latest"
+                    [total]="row.stats.bytes_used.latest + row.stats.avail_raw.latest"
+                    [used]="row.stats.bytes_used.latest"
                     decimals="2">
       </cd-usage-bar>
     </ng-template>
index 3dcff15fcacfe83ed01cbf53d429e79de958b06a..aa487bf909ec02c8f550ce9d563976e036125e14 100644 (file)
                          [selection]="selection">
   </cd-rgw-bucket-details>
 </cd-table>
+
+<ng-template #bucketSizeTpl
+             let-row="row">
+  <cd-usage-bar *ngIf="row.bucket_quota.max_size > 0 && row.bucket_quota.enabled; else noSizeQuota"
+                [total]="row.bucket_quota.max_size"
+                [used]="row.bucket_size">
+  </cd-usage-bar>
+
+  <ng-template #noSizeQuota>No Limit</ng-template>
+</ng-template>
+
+<ng-template #bucketObjectTpl
+             let-row="row">
+  <cd-usage-bar *ngIf="row.bucket_quota.max_objects > 0 && row.bucket_quota.enabled; else noObjectQuota"
+                [total]="row.bucket_quota.max_objects"
+                [used]="row.num_objects"
+                [isBinary]="false">
+  </cd-usage-bar>
+
+  <ng-template #noObjectQuota>No Limit</ng-template>
+</ng-template>
index 2d4736dd71ab7226e86008d00a82f117c69b8255..4ecb6ae1a3ea52493f64f07a6dc815df600e1ef1 100644 (file)
@@ -5,12 +5,14 @@ import { RouterTestingModule } from '@angular/router/testing';
 
 import { ModalModule } from 'ngx-bootstrap/modal';
 import { TabsModule } from 'ngx-bootstrap/tabs';
+import { of } from 'rxjs';
 
 import {
   configureTestBed,
   i18nProviders,
   PermissionHelper
 } from '../../../../testing/unit-test-helper';
+import { RgwBucketService } from '../../../shared/api/rgw-bucket.service';
 import { ActionLabels } from '../../../shared/constants/app.constants';
 import { TableActionsComponent } from '../../../shared/datatable/table-actions/table-actions.component';
 import { SharedModule } from '../../../shared/shared.module';
@@ -20,6 +22,8 @@ import { RgwBucketListComponent } from './rgw-bucket-list.component';
 describe('RgwBucketListComponent', () => {
   let component: RgwBucketListComponent;
   let fixture: ComponentFixture<RgwBucketListComponent>;
+  let rgwBucketService: RgwBucketService;
+  let rgwBucketServiceListSpy: jasmine.Spy;
 
   configureTestBed({
     declarations: [RgwBucketListComponent, RgwBucketDetailsComponent],
@@ -34,6 +38,9 @@ describe('RgwBucketListComponent', () => {
   });
 
   beforeEach(() => {
+    rgwBucketService = TestBed.get(RgwBucketService);
+    rgwBucketServiceListSpy = spyOn(rgwBucketService, 'list');
+    rgwBucketServiceListSpy.and.returnValue(of(null));
     fixture = TestBed.createComponent(RgwBucketListComponent);
     component = fixture.componentInstance;
   });
@@ -199,4 +206,86 @@ describe('RgwBucketListComponent', () => {
       });
     });
   });
+
+  it('should test if bucket data is tranformed correctly', () => {
+    rgwBucketServiceListSpy.and.returnValue(
+      of([
+        {
+          bucket: 'bucket',
+          owner: 'testid',
+          usage: {
+            'rgw.main': {
+              size_actual: 4,
+              num_objects: 2
+            },
+            'rgw.another': {
+              size_actual: 6,
+              num_objects: 6
+            }
+          },
+          bucket_quota: {
+            max_size: 20,
+            max_objects: 10,
+            enabled: true
+          }
+        }
+      ])
+    );
+    fixture.detectChanges();
+    expect(component.buckets).toEqual([
+      {
+        bucket: 'bucket',
+        owner: 'testid',
+        usage: {
+          'rgw.main': { size_actual: 4, num_objects: 2 },
+          'rgw.another': { size_actual: 6, num_objects: 6 }
+        },
+        bucket_quota: {
+          max_size: 20,
+          max_objects: 10,
+          enabled: true
+        },
+        bucket_size: 10,
+        num_objects: 8,
+        size_usage: 0.5,
+        object_usage: 0.8
+      }
+    ]);
+  });
+  it('should usage bars only if quota enabled', () => {
+    rgwBucketServiceListSpy.and.returnValue(
+      of([
+        {
+          bucket: 'bucket',
+          owner: 'testid',
+          bucket_quota: {
+            max_size: 1024,
+            max_objects: 10,
+            enabled: true
+          }
+        }
+      ])
+    );
+    fixture.detectChanges();
+    const usageBars = fixture.debugElement.nativeElement.querySelectorAll('cd-usage-bar');
+    expect(usageBars.length).toBe(2);
+  });
+  it('should not show any usage bars if quota disabled', () => {
+    rgwBucketServiceListSpy.and.returnValue(
+      of([
+        {
+          bucket: 'bucket',
+          owner: 'testid',
+          bucket_quota: {
+            max_size: 1024,
+            max_objects: 10,
+            enabled: false
+          }
+        }
+      ])
+    );
+    fixture.detectChanges();
+    const usageBars = fixture.debugElement.nativeElement.querySelectorAll('cd-usage-bar');
+    expect(usageBars.length).toBe(0);
+  });
 });
index bfdcd04679773647f899aff37d5b0fb9af843fe1..f16a98b48c9384239686c40b0fad95d3460e7b0e 100644 (file)
@@ -1,6 +1,7 @@
-import { Component, ViewChild } from '@angular/core';
+import { ChangeDetectorRef, Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
 
 import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
 import { BsModalService } from 'ngx-bootstrap/modal';
 import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs';
 
@@ -13,6 +14,8 @@ import { CdTableColumn } from '../../../shared/models/cd-table-column';
 import { CdTableFetchDataContext } from '../../../shared/models/cd-table-fetch-data-context';
 import { CdTableSelection } from '../../../shared/models/cd-table-selection';
 import { Permission } from '../../../shared/models/permissions';
+import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
+import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 import { URLBuilderService } from '../../../shared/services/url-builder.service';
 
@@ -24,9 +27,13 @@ const BASE_URL = 'rgw/bucket';
   styleUrls: ['./rgw-bucket-list.component.scss'],
   providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
 })
-export class RgwBucketListComponent {
+export class RgwBucketListComponent implements OnInit {
   @ViewChild(TableComponent)
   table: TableComponent;
+  @ViewChild('bucketSizeTpl')
+  bucketSizeTpl: TemplateRef<any>;
+  @ViewChild('bucketObjectTpl')
+  bucketObjectTpl: TemplateRef<any>;
 
   permission: Permission;
   tableActions: CdTableAction[];
@@ -36,25 +43,16 @@ export class RgwBucketListComponent {
 
   constructor(
     private authStorageService: AuthStorageService,
+    private dimlessBinaryPipe: DimlessBinaryPipe,
+    private dimlessPipe: DimlessPipe,
     private rgwBucketService: RgwBucketService,
     private bsModalService: BsModalService,
     private i18n: I18n,
     private urlBuilder: URLBuilderService,
-    public actionLabels: ActionLabelsI18n
+    public actionLabels: ActionLabelsI18n,
+    private changeDetectorRef: ChangeDetectorRef
   ) {
     this.permission = this.authStorageService.getPermissions().rgw;
-    this.columns = [
-      {
-        name: this.i18n('Name'),
-        prop: 'bid',
-        flexGrow: 1
-      },
-      {
-        name: this.i18n('Owner'),
-        prop: 'owner',
-        flexGrow: 1
-      }
-    ];
     const getBucketUri = () =>
       this.selection.first() && `${encodeURIComponent(this.selection.first().bid)}`;
     const addAction: CdTableAction = {
@@ -78,10 +76,70 @@ export class RgwBucketListComponent {
     this.tableActions = [addAction, editAction, deleteAction];
   }
 
+  ngOnInit() {
+    this.columns = [
+      {
+        name: this.i18n('Name'),
+        prop: 'bid',
+        flexGrow: 2
+      },
+      {
+        name: this.i18n('Owner'),
+        prop: 'owner',
+        flexGrow: 3
+      },
+      {
+        name: this.i18n('Used Capacity'),
+        prop: 'bucket_size',
+        flexGrow: 0.5,
+        pipe: this.dimlessBinaryPipe
+      },
+      {
+        name: this.i18n('Capacity Limit %'),
+        prop: 'size_usage',
+        cellTemplate: this.bucketSizeTpl,
+        flexGrow: 1
+      },
+      {
+        name: this.i18n('Objects'),
+        prop: 'num_objects',
+        flexGrow: 0.5,
+        pipe: this.dimlessPipe
+      },
+      {
+        name: this.i18n('Object Limit %'),
+        prop: 'object_usage',
+        cellTemplate: this.bucketObjectTpl,
+        flexGrow: 1
+      }
+    ];
+  }
+
+  transformBucketData() {
+    _.forEach(this.buckets, (bucketKey) => {
+      const usageList = bucketKey['usage'];
+      const maxBucketSize = bucketKey['bucket_quota']['max_size'];
+      const maxBucketObjects = bucketKey['bucket_quota']['max_objects'];
+      let totalBucketSize = 0;
+      let numOfObjects = 0;
+      _.forEach(usageList, (usageKey) => {
+        totalBucketSize = totalBucketSize + usageKey.size_actual;
+        numOfObjects = numOfObjects + usageKey.num_objects;
+      });
+      bucketKey['bucket_size'] = totalBucketSize;
+      bucketKey['num_objects'] = numOfObjects;
+      bucketKey['size_usage'] = maxBucketSize > 0 ? totalBucketSize / maxBucketSize : undefined;
+      bucketKey['object_usage'] =
+        maxBucketObjects > 0 ? numOfObjects / maxBucketObjects : undefined;
+    });
+  }
+
   getBucketList(context: CdTableFetchDataContext) {
     this.rgwBucketService.list().subscribe(
       (resp: object[]) => {
         this.buckets = resp;
+        this.transformBucketData();
+        this.changeDetectorRef.detectChanges();
       },
       () => {
         context.error();
index 65d9a89d4d8f1e1483e6dc5eba03126a038236a6..c31a2eb4ed13a8e72540ecc7bd22cc3ec201e405 100644 (file)
@@ -2,11 +2,11 @@
   <table>
     <tr>
       <td class="text-left">Used:&nbsp;</td>
-      <td class="text-right"><strong> {{ usedBytes | dimlessBinary }}</strong></td>
+      <td class="text-right"><strong> {{ isBinary ? (used | dimlessBinary) : (used | dimless) }}</strong></td>
     </tr>
     <tr>
       <td class="text-left">Free:&nbsp;</td>
-      <td class="'text-right"><strong>{{ freeBytes | dimlessBinary }}</strong></td>
+      <td class="'text-right"><strong>{{ isBinary ? (total - used | dimlessBinary) : (total - used | dimless) }}</strong></td>
     </tr>
   </table>
 </ng-template>
index 0a3b12e24e5e2abee32dfa3c6a4fe3780ea29b12..0d4256d8728a61a1f2e3112a6cba7e5335ac50ed 100644 (file)
@@ -7,21 +7,21 @@ import { Component, Input, OnChanges } from '@angular/core';
 })
 export class UsageBarComponent implements OnChanges {
   @Input()
-  totalBytes: number;
+  total: number;
   @Input()
-  usedBytes: number;
+  used: number;
   @Input()
   decimals = 0;
+  @Input()
+  isBinary = true;
 
   usedPercentage: number;
   freePercentage: number;
-  freeBytes: number;
 
   constructor() {}
 
   ngOnChanges() {
-    this.usedPercentage = this.totalBytes > 0 ? (this.usedBytes / this.totalBytes) * 100 : 0;
+    this.usedPercentage = this.total > 0 ? (this.used / this.total) * 100 : 0;
     this.freePercentage = 100 - this.usedPercentage;
-    this.freeBytes = this.totalBytes - this.usedBytes;
   }
 }