Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pacific: mgr/dashboard: Display users current quota usage #40360

Merged
merged 1 commit into from Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 9 additions & 2 deletions qa/tasks/mgr/dashboard/test_rgw.py
Expand Up @@ -62,8 +62,8 @@ def tearDownClass(cls):
cls._radosgw_admin_cmd(['user', 'rm', '--uid=teuth-test-user', '--purge-data'])
super(RgwTestCase, cls).tearDownClass()

def get_rgw_user(self, uid):
return self._get('/api/rgw/user/{}'.format(uid))
def get_rgw_user(self, uid, stats=True):
return self._get('/api/rgw/user/{}?stats={}'.format(uid, stats))


class RgwApiCredentialsTest(RgwTestCase):
Expand Down Expand Up @@ -510,6 +510,13 @@ def test_get(self):
self.assertStatus(200)
self._assert_user_data(data)
self.assertEqual(data['user_id'], 'admin')
self.assertTrue(data['stats'])
self.assertIsInstance(data['stats'], dict)
# Test without stats.
data = self.get_rgw_user('admin', False)
self.assertStatus(200)
self._assert_user_data(data)
self.assertEqual(data['user_id'], 'admin')

def test_list(self):
data = self._get('/api/rgw/user')
Expand Down
7 changes: 4 additions & 3 deletions src/pybind/mgr/dashboard/controllers/rgw.py
Expand Up @@ -364,9 +364,10 @@ def list(self, daemon_name=None):
marker = result['marker']
return users

def get(self, uid, daemon_name=None):
# type: (str, Optional[str]) -> dict
result = self.proxy(daemon_name, 'GET', 'user', {'uid': uid})
def get(self, uid, daemon_name=None, stats=True) -> dict:
query_params = '?stats' if stats else ''
result = self.proxy(daemon_name, 'GET', 'user{}'.format(query_params),
{'uid': uid, 'stats': stats})
if not self._keys_allowed():
del result['keys']
del result['swift_keys']
Expand Down
Expand Up @@ -27,7 +27,8 @@
[used]="row.bucket_size">
</cd-usage-bar>

<ng-template #noSizeQuota>No Limit</ng-template>
<ng-template #noSizeQuota
i18n>No Limit</ng-template>
</ng-template>

<ng-template #bucketObjectTpl
Expand All @@ -38,5 +39,6 @@
[isBinary]="false">
</cd-usage-bar>

<ng-template #noObjectQuota>No Limit</ng-template>
<ng-template #noObjectQuota
i18n>No Limit</ng-template>
</ng-template>
Expand Up @@ -33,14 +33,16 @@ describe('RgwBucketListComponent', () => {
beforeEach(() => {
rgwBucketService = TestBed.inject(RgwBucketService);
rgwBucketServiceListSpy = spyOn(rgwBucketService, 'list');
rgwBucketServiceListSpy.and.returnValue(of(null));
rgwBucketServiceListSpy.and.returnValue(of([]));
fixture = TestBed.createComponent(RgwBucketListComponent);
component = fixture.componentInstance;
spyOn(component, 'timeConditionReached').and.stub();
fixture.detectChanges();
});

it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
expect(rgwBucketServiceListSpy).toHaveBeenCalledTimes(1);
});

it('should test all TableActions combinations', () => {
Expand Down Expand Up @@ -109,7 +111,8 @@ describe('RgwBucketListComponent', () => {
}
])
);
fixture.detectChanges();
component.getBucketList(null);
expect(rgwBucketServiceListSpy).toHaveBeenCalledTimes(2);
expect(component.buckets).toEqual([
{
bucket: 'bucket',
Expand All @@ -130,6 +133,7 @@ describe('RgwBucketListComponent', () => {
}
]);
});

it('should usage bars only if quota enabled', () => {
rgwBucketServiceListSpy.and.returnValue(
of([
Expand All @@ -144,10 +148,13 @@ describe('RgwBucketListComponent', () => {
}
])
);
component.getBucketList(null);
expect(rgwBucketServiceListSpy).toHaveBeenCalledTimes(2);
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([
Expand All @@ -162,6 +169,8 @@ describe('RgwBucketListComponent', () => {
}
])
);
component.getBucketList(null);
expect(rgwBucketServiceListSpy).toHaveBeenCalledTimes(2);
fixture.detectChanges();
const usageBars = fixture.debugElement.nativeElement.querySelectorAll('cd-usage-bar');
expect(usageBars.length).toBe(0);
Expand Down
@@ -1,11 +1,4 @@
import {
ChangeDetectorRef,
Component,
NgZone,
OnInit,
TemplateRef,
ViewChild
} from '@angular/core';
import { Component, NgZone, OnInit, TemplateRef, ViewChild } from '@angular/core';

import _ from 'lodash';
import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs';
Expand Down Expand Up @@ -60,39 +53,13 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit {
private modalService: ModalService,
private urlBuilder: URLBuilderService,
public actionLabels: ActionLabelsI18n,
private ngZone: NgZone,
private changeDetectorRef: ChangeDetectorRef
private ngZone: NgZone
) {
super();
this.permission = this.authStorageService.getPermissions().rgw;
const getBucketUri = () =>
this.selection.first() && `${encodeURIComponent(this.selection.first().bid)}`;
const addAction: CdTableAction = {
permission: 'create',
icon: Icons.add,
routerLink: () => this.urlBuilder.getCreate(),
name: this.actionLabels.CREATE,
canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
};
const editAction: CdTableAction = {
permission: 'update',
icon: Icons.edit,
routerLink: () => this.urlBuilder.getEdit(getBucketUri()),
name: this.actionLabels.EDIT
};
const deleteAction: CdTableAction = {
permission: 'delete',
icon: Icons.destroy,
click: () => this.deleteAction(),
disable: () => !this.selection.hasSelection,
name: this.actionLabels.DELETE,
canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection
};
this.tableActions = [addAction, editAction, deleteAction];
this.timeConditionReached();
}

ngOnInit() {
this.permission = this.authStorageService.getPermissions().rgw;
this.columns = [
{
name: $localize`Name`,
Expand Down Expand Up @@ -129,6 +96,31 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit {
flexGrow: 0.8
}
];
const getBucketUri = () =>
this.selection.first() && `${encodeURIComponent(this.selection.first().bid)}`;
const addAction: CdTableAction = {
permission: 'create',
icon: Icons.add,
routerLink: () => this.urlBuilder.getCreate(),
name: this.actionLabels.CREATE,
canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
};
const editAction: CdTableAction = {
permission: 'update',
icon: Icons.edit,
routerLink: () => this.urlBuilder.getEdit(getBucketUri()),
name: this.actionLabels.EDIT
};
const deleteAction: CdTableAction = {
permission: 'delete',
icon: Icons.destroy,
click: () => this.deleteAction(),
disable: () => !this.selection.hasSelection,
name: this.actionLabels.DELETE,
canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection
};
this.tableActions = [addAction, editAction, deleteAction];
this.timeConditionReached();
}

transformBucketData() {
Expand Down Expand Up @@ -171,7 +163,6 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit {
(resp: object[]) => {
this.buckets = resp;
this.transformBucketData();
this.changeDetectorRef.detectChanges();
},
() => {
context.error();
Expand Down
Expand Up @@ -19,3 +19,26 @@
[selection]="expandedRow">
</cd-rgw-user-details>
</cd-table>

<ng-template #userSizeTpl
let-row="row">
<cd-usage-bar *ngIf="row.user_quota.max_size > 0 && row.user_quota.enabled; else noSizeQuota"
[total]="row.user_quota.max_size"
[used]="row.stats.size_actual">
</cd-usage-bar>

<ng-template #noSizeQuota
i18n>No Limit</ng-template>
</ng-template>

<ng-template #userObjectTpl
let-row="row">
<cd-usage-bar *ngIf="row.user_quota.max_objects > 0 && row.user_quota.enabled; else noObjectQuota"
[total]="row.user_quota.max_objects"
[used]="row.stats.num_objects"
[isBinary]="false">
</cd-usage-bar>

<ng-template #noObjectQuota
i18n>No Limit</ng-template>
</ng-template>
Expand Up @@ -4,6 +4,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';

import { of } from 'rxjs';

import { RgwUserService } from '~/app/shared/api/rgw-user.service';
import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
import { SharedModule } from '~/app/shared/shared.module';
import { configureTestBed, PermissionHelper } from '~/testing/unit-test-helper';
Expand All @@ -12,6 +15,8 @@ import { RgwUserListComponent } from './rgw-user-list.component';
describe('RgwUserListComponent', () => {
let component: RgwUserListComponent;
let fixture: ComponentFixture<RgwUserListComponent>;
let rgwUserService: RgwUserService;
let rgwUserServiceListSpy: jasmine.Spy;

configureTestBed({
declarations: [RgwUserListComponent],
Expand All @@ -20,13 +25,18 @@ describe('RgwUserListComponent', () => {
});

beforeEach(() => {
rgwUserService = TestBed.inject(RgwUserService);
rgwUserServiceListSpy = spyOn(rgwUserService, 'list');
rgwUserServiceListSpy.and.returnValue(of([]));
fixture = TestBed.createComponent(RgwUserListComponent);
component = fixture.componentInstance;
spyOn(component, 'timeConditionReached').and.stub();
fixture.detectChanges();
});

it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
expect(rgwUserServiceListSpy).toHaveBeenCalledTimes(1);
});

it('should test all TableActions combinations', () => {
Expand Down Expand Up @@ -70,4 +80,87 @@ describe('RgwUserListComponent', () => {
}
});
});

it('should test if rgw-user data is tranformed correctly', () => {
rgwUserServiceListSpy.and.returnValue(
of([
{
user_id: 'testid',
stats: {
size_actual: 6,
num_objects: 6
},
user_quota: {
max_size: 20,
max_objects: 10,
enabled: true
}
}
])
);
component.getUserList(null);
expect(rgwUserServiceListSpy).toHaveBeenCalledTimes(2);
expect(component.users).toEqual([
{
user_id: 'testid',
stats: {
size_actual: 6,
num_objects: 6
},
user_quota: {
max_size: 20,
max_objects: 10,
enabled: true
}
}
]);
});

it('should usage bars only if quota enabled', () => {
rgwUserServiceListSpy.and.returnValue(
of([
{
user_id: 'testid',
stats: {
size_actual: 6,
num_objects: 6
},
user_quota: {
max_size: 1024,
max_objects: 10,
enabled: true
}
}
])
);
component.getUserList(null);
expect(rgwUserServiceListSpy).toHaveBeenCalledTimes(2);
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', () => {
rgwUserServiceListSpy.and.returnValue(
of([
{
user_id: 'testid',
stats: {
size_actual: 6,
num_objects: 6
},
user_quota: {
max_size: 1024,
max_objects: 10,
enabled: false
}
}
])
);
component.getUserList(null);
expect(rgwUserServiceListSpy).toHaveBeenCalledTimes(2);
fixture.detectChanges();
const usageBars = fixture.debugElement.nativeElement.querySelectorAll('cd-usage-bar');
expect(usageBars.length).toBe(0);
});
});