group['info'] = json.loads(out)
return subvolume_groups
+ @RESTController.Resource('GET')
+ def info(self, vol_name: str, group_name: str):
+ 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}'
+ )
+ return json.loads(out)
+
def create(self, vol_name: str, group_name: str, **kwargs):
error_code, _, err = mgr.remote('volumes', '_cmd_fs_subvolumegroup_create', None, {
'vol_name': vol_name, 'group_name': group_name, **kwargs})
raise DashboardException(
f'Failed to create subvolume group {group_name}: {err}'
)
+
+ def set(self, vol_name: str, group_name: str, size: str):
+ if not size:
+ return f'Failed to update subvolume group {group_name}, size was not provided'
+ error_code, _, err = mgr.remote('volumes', '_cmd_fs_subvolumegroup_resize', None, {
+ 'vol_name': vol_name, 'group_name': group_name, 'new_size': size})
+ if error_code != 0:
+ raise DashboardException(
+ f'Failed to update subvolume group {group_name}: {err}'
+ )
+ return f'Subvolume group {group_name} updated successfully'
[columns]="columns"
selectionType="single"
[hasDetails]="false"
- (fetchData)="fetchData()">
+ (fetchData)="fetchData()"
+ (updateSelection)="updateSelection($event)">
<div class="table-actions btn-toolbar">
<cd-table-actions [permission]="permissions.cephfs"
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"
+ [used]="row.info.bytes_used"
[title]="row.name"
- [calculatePerc]="false"
+ [showFreeToolTip]="false"
customLegend="Quota"
[customLegendValue]="row.info.bytes_quota"
decimals="2"></cd-usage-bar>
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';
+import { CephfsSubvolumeGroup } from '~/app/shared/models/cephfs-subvolumegroup.model';
import { CephfsSubvolumegroupFormComponent } from '../cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component';
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
name: this.actionLabels.CREATE,
permission: 'create',
icon: Icons.add,
- click: () =>
- this.modalService.show(
- CephfsSubvolumegroupFormComponent,
- {
- fsName: this.fsName,
- pools: this.pools
- },
- { size: 'lg' }
- )
+ click: () => this.openModal(),
+ canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
+ },
+ {
+ name: this.actionLabels.EDIT,
+ permission: 'update',
+ icon: Icons.edit,
+ click: () => this.openModal(true)
}
];
updateSelection(selection: CdTableSelection) {
this.selection = selection;
}
+
+ openModal(edit = false) {
+ this.modalService.show(
+ CephfsSubvolumegroupFormComponent,
+ {
+ fsName: this.fsName,
+ subvolumegroupName: this.selection?.first()?.name,
+ pools: this.pools,
+ isEdit: edit
+ },
+ { size: 'lg' }
+ );
+ }
}
<ng-container i18n="form title"
class="modal-title">{{ action | titlecase }} {{ resource | upperFirst }}</ng-container>
- <ng-container class="modal-content">
+ <ng-container class="modal-content"
+ *cdFormLoading="loading">
<form name="subvolumegroupForm"
#formDir="ngForm"
[formGroup]="subvolumegroupForm"
i18n>This field is required.</span>
<span class="invalid-feedback"
*ngIf="subvolumegroupForm.showError('subvolumegroupName', formDir, 'notUnique')"
- i18n>The subvolumegroup already exists.</span>
+ i18n>The subvolume group already exists.</span>
</div>
</div>
<label class="cd-col-form-label"
for="size"
i18n>Size
- <cd-helper>The size of the subvolumegropup is specified by setting a quota on it.
- If left blank or put 0, then quota will be infinite</cd-helper>
+ <cd-helper>The size of the subvolume group is specified by setting a quota on it.
+ If left blank or put 0, then quota will be infinite</cd-helper>
</label>
<div class="cd-col-form-input">
<input class="form-control"
[form]="subvolumegroupForm"
inputField="mode"
[isTableForOctalMode]="true"
- [scopes]="scopes"></cd-checked-table-form>
+ [initialValue]="initialMode"
+ [scopes]="scopes"
+ [isDisabled]="isEdit"></cd-checked-table-form>
</div>
</div>
</div>
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
import _ from 'lodash';
import { CdValidators } from '~/app/shared/forms/cd-validators';
+import { CdForm } from '~/app/shared/forms/cd-form';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+import { OctalToHumanReadablePipe } from '~/app/shared/pipes/octal-to-human-readable.pipe';
@Component({
selector: 'cd-cephfs-subvolumegroup-form',
templateUrl: './cephfs-subvolumegroup-form.component.html',
styleUrls: ['./cephfs-subvolumegroup-form.component.scss']
})
-export class CephfsSubvolumegroupFormComponent implements OnInit {
+export class CephfsSubvolumegroupFormComponent extends CdForm implements OnInit {
fsName: string;
+ subvolumegroupName: string;
pools: Pool[];
+ isEdit: boolean = false;
subvolumegroupForm: CdFormGroup;
columns: CdTableColumn[];
scopePermissions: Array<any> = [];
+ initialMode = {
+ owner: ['read', 'write', 'execute'],
+ group: ['read', 'execute'],
+ others: ['read', 'execute']
+ };
scopes: string[] = ['owner', 'group', 'others'];
constructor(
private actionLabels: ActionLabelsI18n,
private taskWrapper: TaskWrapperService,
private cephfsSubvolumeGroupService: CephfsSubvolumeGroupService,
- private formatter: FormatterService
+ private formatter: FormatterService,
+ private dimlessBinary: DimlessBinaryPipe,
+ private octalToHumanReadable: OctalToHumanReadablePipe
) {
- this.action = this.actionLabels.CREATE;
+ super();
this.resource = $localize`subvolume group`;
}
ngOnInit(): void {
+ this.action = this.actionLabels.CREATE;
this.columns = [
{
prop: 'scope',
this.dataPools = this.pools.filter((pool) => pool.type === 'data');
this.createForm();
+
+ this.isEdit ? this.populateForm() : this.loadingReady();
}
createForm() {
});
}
+ populateForm() {
+ this.action = this.actionLabels.EDIT;
+ this.cephfsSubvolumeGroupService
+ .info(this.fsName, this.subvolumegroupName)
+ .subscribe((resp: any) => {
+ // Disabled these fields since its not editable
+ this.subvolumegroupForm.get('subvolumegroupName').disable();
+ this.subvolumegroupForm.get('pool').disable();
+ this.subvolumegroupForm.get('uid').disable();
+ this.subvolumegroupForm.get('gid').disable();
+
+ this.subvolumegroupForm.get('subvolumegroupName').setValue(this.subvolumegroupName);
+ if (resp.bytes_quota !== 'infinite') {
+ this.subvolumegroupForm
+ .get('size')
+ .setValue(this.dimlessBinary.transform(resp.bytes_quota));
+ }
+ this.subvolumegroupForm.get('uid').setValue(resp.uid);
+ this.subvolumegroupForm.get('gid').setValue(resp.gid);
+ this.initialMode = this.octalToHumanReadable.transform(resp.mode, true);
+
+ this.loadingReady();
+ });
+ }
+
submit() {
const subvolumegroupName = this.subvolumegroupForm.getValue('subvolumegroupName');
const pool = this.subvolumegroupForm.getValue('pool');
- const size = this.formatter.toBytes(this.subvolumegroupForm.getValue('size'));
+ const size = this.formatter.toBytes(this.subvolumegroupForm.getValue('size')) || 0;
const uid = this.subvolumegroupForm.getValue('uid');
const gid = this.subvolumegroupForm.getValue('gid');
const mode = this.formatter.toOctalPermission(this.subvolumegroupForm.getValue('mode'));
- this.taskWrapper
- .wrapTaskAroundCall({
- task: new FinishedTask('cephfs/subvolume/group/' + URLVerbs.CREATE, {
- subvolumegroupName: subvolumegroupName
- }),
- call: this.cephfsSubvolumeGroupService.create(
- this.fsName,
- subvolumegroupName,
- pool,
- size,
- uid,
- gid,
- mode
- )
- })
- .subscribe({
- error: () => {
- this.subvolumegroupForm.setErrors({ cdSubmitButton: true });
- },
- complete: () => {
- this.activeModal.close();
- }
- });
+ if (this.isEdit) {
+ const editSize = size === 0 ? 'infinite' : size;
+ this.taskWrapper
+ .wrapTaskAroundCall({
+ task: new FinishedTask('cephfs/subvolume/group/' + URLVerbs.EDIT, {
+ subvolumegroupName: subvolumegroupName
+ }),
+ call: this.cephfsSubvolumeGroupService.update(
+ this.fsName,
+ subvolumegroupName,
+ String(editSize)
+ )
+ })
+ .subscribe({
+ error: () => {
+ this.subvolumegroupForm.setErrors({ cdSubmitButton: true });
+ },
+ complete: () => {
+ this.activeModal.close();
+ }
+ });
+ } else {
+ this.taskWrapper
+ .wrapTaskAroundCall({
+ task: new FinishedTask('cephfs/subvolume/group/' + URLVerbs.CREATE, {
+ subvolumegroupName: subvolumegroupName
+ }),
+ call: this.cephfsSubvolumeGroupService.create(
+ this.fsName,
+ subvolumegroupName,
+ pool,
+ String(size),
+ uid,
+ gid,
+ mode
+ )
+ })
+ .subscribe({
+ error: () => {
+ this.subvolumegroupForm.setErrors({ cdSubmitButton: true });
+ },
+ complete: () => {
+ this.activeModal.close();
+ }
+ });
+ }
}
}
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
-import { CephfsSubvolumeGroup } from '../models/cephfs-subvolume-group.model';
+import { CephfsSubvolumeGroup } from '../models/cephfs-subvolumegroup.model';
import _ from 'lodash';
import { mapTo, catchError } from 'rxjs/operators';
volName: string,
groupName: string,
poolName: string,
- size: number,
+ size: string,
uid: number,
gid: number,
mode: string
})
);
}
+
+ update(volName: string, groupName: string, size: string) {
+ return this.http.put(`${this.baseURL}/${volName}`, {
+ group_name: groupName,
+ size: size
+ });
+ }
}
--- /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;
+}
),
'cephfs/subvolume/group/create': this.newTaskMessage(this.commonOperations.create, (metadata) =>
this.subvolumegroup(metadata)
+ ),
+ 'cephfs/subvolume/group/edit': this.newTaskMessage(this.commonOperations.update, (metadata) =>
+ this.subvolumegroup(metadata)
)
};
- jwt: []
tags:
- CephfsSubvolumeGroup
+ put:
+ parameters:
+ - in: path
+ name: vol_name
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ group_name:
+ type: string
+ size:
+ type: integer
+ required:
+ - group_name
+ - size
+ type: object
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource updated.
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '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/group/{vol_name}/info:
+ get:
+ parameters:
+ - in: path
+ name: vol_name
+ required: true
+ schema:
+ type: string
+ - in: query
+ name: group_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}:
delete:
parameters: