)
return f'Subvolume {subvol_name} created successfully'
+
+
+@APIRouter('/cephfs/subvolume/group', Scope.CEPHFS)
+@APIDoc("Cephfs Subvolume Group Management API", "CephfsSubvolumeGroup")
+class CephFSSubvolumeGroups(RESTController):
+
+ def get(self, vol_name):
+ if not vol_name:
+ raise DashboardException(
+ 'Error listing subvolume groups')
+ error_code, out, err = mgr.remote('volumes', '_cmd_fs_subvolumegroup_ls',
+ None, {'vol_name': vol_name})
+ if error_code != 0:
+ raise DashboardException(
+ 'Error listing subvolume groups')
+ subvolume_groups = json.loads(out)
+ for group in subvolume_groups:
+ error_code, out, err = mgr.remote('volumes', '_cmd_fs_subvolumegroup_info',
+ None, {'vol_name': vol_name,
+ 'group_name': group['name']})
+ if error_code != 0:
+ raise DashboardException(
+ f'Failed to get info for subvolume group {group["name"]}: {err}'
+ )
+ group['info'] = json.loads(out)
+ return subvolume_groups
--- /dev/null
+
+<ng-container *ngIf="subvolumeGroup$ | async as subvolumeGroup">
+ <cd-table *ngIf="subvolumeGroup"
+ [data]="subvolumeGroup"
+ columnMode="flex"
+ [columns]="columns"
+ selectionType="single"
+ [hasDetails]="false">
+ </cd-table>
+</ng-container>
+
+<ng-template #quotaUsageTpl
+ let-row="row">
+ <cd-usage-bar *ngIf="row.info.bytes_pcent && row.info.bytes_pcent !== 'undefined'; else noLimitTpl"
+ [total]="row.info.bytes_quota"
+ [used]="row.info.bytes_pcent"
+ [title]="row.name"
+ [calculatePerc]="false"
+ customLegend="Quota"
+ [customLegendValue]="row.info.bytes_quota"
+ decimals="2"></cd-usage-bar>
+
+ <ng-template #noLimitTpl>
+ <span ngbTooltip="Quota limit is not set"
+ *ngIf="row.info.bytes_pcent === 'undefined'"
+ i18n-ngbTooltip>
+ {{row.info.bytes_used | dimlessBinary}}</span>
+ </ng-template>
+</ng-template>
+
+<ng-template #typeTpl
+ let-value="value">
+ <cd-label [value]="value"></cd-label>
+</ng-template>
+
+<ng-template #modeToHumanReadableTpl
+ let-value="value">
+ <span *ngFor="let result of (value | octalToHumanReadable)"
+ [ngClass]="result.class"
+ [ngbTooltip]="result.toolTip">
+ {{ result.content }}
+ </span>
+</ng-template>
--- /dev/null
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CephfsSubvolumeGroupComponent } from './cephfs-subvolume-group.component';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+describe('CephfsSubvolumeGroupComponent', () => {
+ let component: CephfsSubvolumeGroupComponent;
+ let fixture: ComponentFixture<CephfsSubvolumeGroupComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [CephfsSubvolumeGroupComponent],
+ imports: [HttpClientTestingModule]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CephfsSubvolumeGroupComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component, Input, OnInit, ViewChild } from '@angular/core';
+import { Observable, of } from 'rxjs';
+import { catchError } from 'rxjs/operators';
+
+import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-group.service';
+import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { CephfsSubvolumeGroup } from '~/app/shared/models/cephfs-subvolume-group.model';
+
+@Component({
+ selector: 'cd-cephfs-subvolume-group',
+ templateUrl: './cephfs-subvolume-group.component.html',
+ styleUrls: ['./cephfs-subvolume-group.component.scss']
+})
+export class CephfsSubvolumeGroupComponent implements OnInit {
+ @ViewChild('quotaUsageTpl', { static: true })
+ quotaUsageTpl: any;
+
+ @ViewChild('typeTpl', { static: true })
+ typeTpl: any;
+
+ @ViewChild('modeToHumanReadableTpl', { static: true })
+ modeToHumanReadableTpl: any;
+
+ @ViewChild('nameTpl', { static: true })
+ nameTpl: any;
+
+ @ViewChild('quotaSizeTpl', { static: true })
+ quotaSizeTpl: any;
+
+ @Input()
+ fsName: any;
+
+ columns: CdTableColumn[];
+ context: CdTableFetchDataContext;
+ selection = new CdTableSelection();
+
+ subvolumeGroup$: Observable<CephfsSubvolumeGroup[]>;
+
+ constructor(private cephfsSubvolumeGroup: CephfsSubvolumeGroupService) {}
+
+ ngOnInit(): void {
+ this.columns = [
+ {
+ name: $localize`Name`,
+ prop: 'name',
+ flexGrow: 0.6,
+ cellTransformation: CellTemplate.bold
+ },
+ {
+ name: $localize`Data Pool`,
+ prop: 'info.data_pool',
+ flexGrow: 0.7,
+ cellTransformation: CellTemplate.badge,
+ customTemplateConfig: {
+ class: 'badge-background-primary'
+ }
+ },
+ {
+ name: $localize`Usage`,
+ prop: 'info.bytes_pcent',
+ flexGrow: 0.7,
+ cellTemplate: this.quotaUsageTpl,
+ cellClass: 'text-right'
+ },
+ {
+ name: $localize`Mode`,
+ prop: 'info.mode',
+ flexGrow: 0.5,
+ cellTemplate: this.modeToHumanReadableTpl
+ },
+ {
+ name: $localize`Created`,
+ prop: 'info.created_at',
+ flexGrow: 0.5,
+ cellTransformation: CellTemplate.timeAgo
+ }
+ ];
+ }
+
+ ngOnChanges() {
+ this.subvolumeGroup$ = this.cephfsSubvolumeGroup.get(this.fsName).pipe(
+ catchError(() => {
+ this.context.error();
+ return of(null);
+ })
+ );
+ }
+
+ updateSelection(selection: CdTableSelection) {
+ this.selection = selection;
+ }
+}
[pools]="details.pools"></cd-cephfs-subvolume-list>
</ng-template>
</ng-container>
+ <ng-container ngbNavItem="subvolume-groups">
+ <a ngbNavLink
+ i18n>Subvolume groups</a>
+ <ng-template ngbNavContent>
+ <cd-cephfs-subvolume-group [fsName]="selection.mdsmap.fs_name">
+ </cd-cephfs-subvolume-group>
+ </ng-template>
+ </ng-container>
<ng-container ngbNavItem="clients">
<a ngbNavLink>
<ng-container i18n>Clients</ng-container>
import { CephfsTabsComponent } from './cephfs-tabs/cephfs-tabs.component';
import { CephfsSubvolumeListComponent } from './cephfs-subvolume-list/cephfs-subvolume-list.component';
import { CephfsSubvolumeFormComponent } from './cephfs-subvolume-form/cephfs-subvolume-form.component';
+import { CephfsSubvolumeGroupComponent } from './cephfs-subvolume-group/cephfs-subvolume-group.component';
@NgModule({
imports: [
CephfsVolumeFormComponent,
CephfsDirectoriesComponent,
CephfsSubvolumeListComponent,
- CephfsSubvolumeFormComponent
+ CephfsSubvolumeFormComponent,
+ CephfsDirectoriesComponent,
+ CephfsSubvolumeGroupComponent
]
})
export class CephfsModule {}
--- /dev/null
+import { TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { CephfsSubvolumeGroupService } from './cephfs-subvolume-group.service';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+describe('CephfsSubvolumeGroupService', () => {
+ let service: CephfsSubvolumeGroupService;
+
+ configureTestBed({
+ imports: [HttpClientTestingModule],
+ providers: [CephfsSubvolumeGroupService]
+ });
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(CephfsSubvolumeGroupService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
--- /dev/null
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { CephfsSubvolumeGroup } from '../models/cephfs-subvolume-group.model';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class CephfsSubvolumeGroupService {
+ baseURL = 'api/cephfs/subvolume/group';
+
+ constructor(private http: HttpClient) {}
+
+ get(volName: string): Observable<CephfsSubvolumeGroup[]> {
+ return this.http.get<CephfsSubvolumeGroup[]>(`${this.baseURL}/${volName}`);
+ }
+}
--- /dev/null
+export interface CephfsSubvolumeGroup {
+ name: string;
+ info: CephfsSubvolumeGroupInfo;
+}
+
+export interface CephfsSubvolumeGroupInfo {
+ mode: number;
+ bytes_pcent: number;
+ bytes_quota: number;
+ data_pool: string;
+ state: string;
+ created_at: string;
+}
- jwt: []
tags:
- CephFSSubvolume
+ /api/cephfs/subvolume/group/{vol_name}:
+ get:
+ parameters:
+ - in: path
+ name: vol_name
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - CephfsSubvolumeGroup
/api/cephfs/subvolume/{vol_name}:
get:
parameters:
name: CephFSSubvolume
- description: Cephfs Management API
name: Cephfs
+- description: Cephfs Subvolume Group Management API
+ name: CephfsSubvolumeGroup
- description: Get Cluster Details
name: Cluster
- description: Manage Cluster Configurations