]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: create subvolumegroup 52886/head
authorPedro Gonzalez Gomez <pegonzal@redhat.com>
Wed, 9 Aug 2023 00:05:40 +0000 (02:05 +0200)
committerPedro Gonzalez Gomez <pegonzal@redhat.com>
Thu, 10 Aug 2023 07:12:25 +0000 (09:12 +0200)
Fixes: https://tracker.ceph.com/issues/62363
Signed-off-by: Pedro Gonzalez Gomez <pegonzal@redhat.com>
12 files changed:
src/pybind/mgr/dashboard/controllers/cephfs.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-group/cephfs-subvolume-group.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-group/cephfs-subvolume-group.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-subvolume-group.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts
src/pybind/mgr/dashboard/openapi.yaml

index 2f72b02905c1757ba6ebd9a6b91776e525d9a85a..79a4c5d4645abf9fc8d14bf33c3877c23bd0b751 100644 (file)
@@ -683,12 +683,12 @@ class CephFSSubvolumeGroups(RESTController):
     def get(self, vol_name):
         if not vol_name:
             raise DashboardException(
-                'Error listing subvolume groups')
+                f'Error listing subvolume groups for {vol_name}')
         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')
+                f'Error listing subvolume groups for {vol_name}')
         subvolume_groups = json.loads(out)
         for group in subvolume_groups:
             error_code, out, err = mgr.remote('volumes', '_cmd_fs_subvolumegroup_info',
@@ -700,3 +700,11 @@ class CephFSSubvolumeGroups(RESTController):
                 )
             group['info'] = json.loads(out)
         return subvolume_groups
+
+    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})
+        if error_code != 0:
+            raise DashboardException(
+                f'Failed to create subvolume group {group_name}: {err}'
+            )
index 27cdc155424e61f053240f6745d2ba689d6d97f6..653bd77a0c7689118e2a9d0dc102e75529dbd47f 100644 (file)
@@ -5,7 +5,17 @@
             columnMode="flex"
             [columns]="columns"
             selectionType="single"
-            [hasDetails]="false">
+            [hasDetails]="false"
+            (fetchData)="fetchData()">
+
+    <div class="table-actions btn-toolbar">
+      <cd-table-actions [permission]="permissions.cephfs"
+                        [selection]="selection"
+                        class="btn-group"
+                        id="cephfs-subvolumegropup-actions"
+                        [tableActions]="tableActions">
+      </cd-table-actions>
+    </div>
   </cd-table>
 </ng-container>
 
index 63a36f27bc0b9b68ee18b7342ff7a3a4fb90cb47..e1fc307afaf99221b1f58e32ba1b9f426adf7c81 100644 (file)
@@ -1,13 +1,20 @@
 import { Component, Input, OnInit, ViewChild } from '@angular/core';
-import { Observable, of } from 'rxjs';
-import { catchError } from 'rxjs/operators';
+import { Observable, ReplaySubject, of } from 'rxjs';
+import { catchError, shareReplay, switchMap } from 'rxjs/operators';
 
 import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-group.service';
 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
 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 { 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';
+import { ModalService } from '~/app/shared/services/modal.service';
+import { Permissions } from '~/app/shared/models/permissions';
 
 @Component({
   selector: 'cd-cephfs-subvolume-group',
@@ -32,14 +39,26 @@ export class CephfsSubvolumeGroupComponent implements OnInit {
 
   @Input()
   fsName: any;
+  @Input() pools: any[];
 
   columns: CdTableColumn[];
+  tableActions: CdTableAction[];
   context: CdTableFetchDataContext;
   selection = new CdTableSelection();
+  icons = Icons;
+  permissions: Permissions;
 
   subvolumeGroup$: Observable<CephfsSubvolumeGroup[]>;
+  subject = new ReplaySubject<CephfsSubvolumeGroup[]>();
 
-  constructor(private cephfsSubvolumeGroup: CephfsSubvolumeGroupService) {}
+  constructor(
+    private cephfsSubvolumeGroup: CephfsSubvolumeGroupService,
+    private actionLabels: ActionLabelsI18n,
+    private modalService: ModalService,
+    private authStorageService: AuthStorageService
+  ) {
+    this.permissions = this.authStorageService.getPermissions();
+  }
 
   ngOnInit(): void {
     this.columns = [
@@ -78,15 +97,43 @@ export class CephfsSubvolumeGroupComponent implements OnInit {
         cellTransformation: CellTemplate.timeAgo
       }
     ];
+
+    this.tableActions = [
+      {
+        name: this.actionLabels.CREATE,
+        permission: 'create',
+        icon: Icons.add,
+        click: () =>
+          this.modalService.show(
+            CephfsSubvolumegroupFormComponent,
+            {
+              fsName: this.fsName,
+              pools: this.pools
+            },
+            { size: 'lg' }
+          )
+      }
+    ];
+
+    this.subvolumeGroup$ = this.subject.pipe(
+      switchMap(() =>
+        this.cephfsSubvolumeGroup.get(this.fsName).pipe(
+          catchError(() => {
+            this.context.error();
+            return of(null);
+          })
+        )
+      ),
+      shareReplay(1)
+    );
+  }
+
+  fetchData() {
+    this.subject.next();
   }
 
   ngOnChanges() {
-    this.subvolumeGroup$ = this.cephfsSubvolumeGroup.get(this.fsName).pipe(
-      catchError(() => {
-        this.context.error();
-        return of(null);
-      })
-    );
+    this.subject.next();
   }
 
   updateSelection(selection: CdTableSelection) {
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.html
new file mode 100644 (file)
index 0000000..b3a26cb
--- /dev/null
@@ -0,0 +1,139 @@
+<cd-modal [modalRef]="activeModal">
+  <ng-container i18n="form title"
+                class="modal-title">{{ action | titlecase }} {{ resource | upperFirst }}</ng-container>
+
+  <ng-container class="modal-content">
+    <form name="subvolumegroupForm"
+          #formDir="ngForm"
+          [formGroup]="subvolumegroupForm"
+          novalidate>
+      <div class="modal-body">
+        <div class="form-group row">
+          <label class="cd-col-form-label required"
+                 for="subvolumegroupName"
+                 i18n>Name</label>
+          <div class="cd-col-form-input">
+            <input class="form-control"
+                   type="text"
+                   placeholder="subvolumegroup name..."
+                   id="subvolumegroupName"
+                   name="subvolumegroupName"
+                   formControlName="subvolumegroupName"
+                   autofocus>
+            <span class="invalid-feedback"
+                  *ngIf="subvolumegroupForm.showError('subvolumegroupName', formDir, 'required')"
+                  i18n>This field is required.</span>
+            <span class="invalid-feedback"
+                  *ngIf="subvolumegroupForm.showError('subvolumegroupName', formDir, 'notUnique')"
+                  i18n>The subvolumegroup already exists.</span>
+          </div>
+        </div>
+
+        <!-- Volume name -->
+        <div class="form-group row">
+          <label class="cd-col-form-label"
+                 for="volumeName"
+                 i18n>Volume name</label>
+          <div class="cd-col-form-input">
+            <input class="form-control"
+                   id="volumeName"
+                   name="volumeName"
+                   formControlName="volumeName">
+          </div>
+        </div>
+
+        <!-- Size -->
+        <div class="form-group row">
+          <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>
+          </label>
+          <div class="cd-col-form-input">
+            <input class="form-control"
+                   type="text"
+                   id="size"
+                   name="size"
+                   formControlName="size"
+                   i18n-placeholder
+                   placeholder="e.g., 10GiB"
+                   defaultUnit="GiB"
+                   cdDimlessBinary>
+          </div>
+        </div>
+
+        <!-- CephFS Pools -->
+        <div class="form-group row">
+          <label class="cd-col-form-label"
+                 for="pool"
+                 i18n>Pool
+            <cd-helper>By default, the data_pool_layout of the parent directory is selected.</cd-helper>
+          </label>
+          <div class="cd-col-form-input">
+            <select class="form-select"
+                    id="pool"
+                    name="pool"
+                    formControlName="pool">
+              <option *ngFor="let pool of dataPools"
+                      [value]="pool.pool">{{ pool.pool }}</option>
+            </select>
+          </div>
+        </div>
+
+        <!-- UID -->
+        <div class="form-group row">
+          <label class="cd-col-form-label"
+                 for="uid"
+                 i18n>UID</label>
+          <div class="cd-col-form-input">
+            <input class="form-control"
+                   type="number"
+                   placeholder="subvolumegroup UID..."
+                   id="uid"
+                   name="uid"
+                   formControlName="uid">
+          </div>
+        </div>
+
+        <!-- GID -->
+        <div class="form-group row">
+          <label class="cd-col-form-label"
+                 for="gid"
+                 i18n>GID</label>
+          <div class="cd-col-form-input">
+            <input class="form-control"
+                   type="number"
+                   placeholder="subvolumegroup GID..."
+                   id="gid"
+                   name="gid"
+                   formControlName="gid">
+          </div>
+        </div>
+
+        <!-- Mode -->
+        <div class="form-group row">
+          <label class="cd-col-form-label"
+                 for="mode"
+                 i18n>Mode
+            <cd-helper>Permissions for the directory. Default mode is 755 which is rwxr-xr-x</cd-helper>
+          </label>
+          <div class="cd-col-form-input">
+            <cd-checked-table-form [data]="scopePermissions"
+                                   [columns]="columns"
+                                   [form]="subvolumegroupForm"
+                                   inputField="mode"
+                                   [isTableForOctalMode]="true"
+                                   [scopes]="scopes"></cd-checked-table-form>
+          </div>
+        </div>
+      </div>
+
+      <div class="modal-footer">
+        <cd-form-button-panel (submitActionEvent)="submit()"
+                              [form]="subvolumegroupForm"
+                              [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"></cd-form-button-panel>
+      </div>
+    </form>
+  </ng-container>
+</cd-modal>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.spec.ts
new file mode 100644 (file)
index 0000000..ea5b63e
--- /dev/null
@@ -0,0 +1,39 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CephfsSubvolumegroupFormComponent } from './cephfs-subvolumegroup-form.component';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { ToastrModule } from 'ngx-toastr';
+import { SharedModule } from '~/app/shared/shared.module';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+
+describe('CephfsSubvolumegroupFormComponent', () => {
+  let component: CephfsSubvolumegroupFormComponent;
+  let fixture: ComponentFixture<CephfsSubvolumegroupFormComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [CephfsSubvolumegroupFormComponent],
+      providers: [NgbActiveModal],
+      imports: [
+        SharedModule,
+        ToastrModule.forRoot(),
+        ReactiveFormsModule,
+        HttpClientTestingModule,
+        RouterTestingModule
+      ]
+    }).compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CephfsSubvolumegroupFormComponent);
+    component = fixture.componentInstance;
+    component.pools = [];
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component.ts
new file mode 100644 (file)
index 0000000..3168889
--- /dev/null
@@ -0,0 +1,135 @@
+import { Component, OnInit } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-group.service';
+import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { FinishedTask } from '~/app/shared/models/finished-task';
+import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
+import { Pool } from '../../pool/pool';
+import { FormatterService } from '~/app/shared/services/formatter.service';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import _ from 'lodash';
+import { CdValidators } from '~/app/shared/forms/cd-validators';
+
+@Component({
+  selector: 'cd-cephfs-subvolumegroup-form',
+  templateUrl: './cephfs-subvolumegroup-form.component.html',
+  styleUrls: ['./cephfs-subvolumegroup-form.component.scss']
+})
+export class CephfsSubvolumegroupFormComponent implements OnInit {
+  fsName: string;
+  pools: Pool[];
+
+  subvolumegroupForm: CdFormGroup;
+
+  action: string;
+  resource: string;
+
+  dataPools: Pool[];
+
+  columns: CdTableColumn[];
+  scopePermissions: Array<any> = [];
+  scopes: string[] = ['owner', 'group', 'others'];
+
+  constructor(
+    public activeModal: NgbActiveModal,
+    private actionLabels: ActionLabelsI18n,
+    private taskWrapper: TaskWrapperService,
+    private cephfsSubvolumeGroupService: CephfsSubvolumeGroupService,
+    private formatter: FormatterService
+  ) {
+    this.action = this.actionLabels.CREATE;
+    this.resource = $localize`subvolume group`;
+  }
+
+  ngOnInit(): void {
+    this.columns = [
+      {
+        prop: 'scope',
+        name: $localize`All`,
+        flexGrow: 0.5
+      },
+      {
+        prop: 'read',
+        name: $localize`Read`,
+        flexGrow: 0.5,
+        cellClass: 'text-center'
+      },
+      {
+        prop: 'write',
+        name: $localize`Write`,
+        flexGrow: 0.5,
+        cellClass: 'text-center'
+      },
+      {
+        prop: 'execute',
+        name: $localize`Execute`,
+        flexGrow: 0.5,
+        cellClass: 'text-center'
+      }
+    ];
+
+    this.dataPools = this.pools.filter((pool) => pool.type === 'data');
+    this.createForm();
+  }
+
+  createForm() {
+    this.subvolumegroupForm = new CdFormGroup({
+      volumeName: new FormControl({ value: this.fsName, disabled: true }),
+      subvolumegroupName: new FormControl('', {
+        validators: [Validators.required],
+        asyncValidators: [
+          CdValidators.unique(
+            this.cephfsSubvolumeGroupService.exists,
+            this.cephfsSubvolumeGroupService,
+            null,
+            null,
+            this.fsName
+          )
+        ]
+      }),
+      pool: new FormControl(this.dataPools[0]?.pool, {
+        validators: [Validators.required]
+      }),
+      size: new FormControl(null, {
+        updateOn: 'blur'
+      }),
+      uid: new FormControl(null),
+      gid: new FormControl(null),
+      mode: new FormControl({})
+    });
+  }
+
+  submit() {
+    const subvolumegroupName = this.subvolumegroupForm.getValue('subvolumegroupName');
+    const pool = this.subvolumegroupForm.getValue('pool');
+    const size = this.formatter.toBytes(this.subvolumegroupForm.getValue('size'));
+    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();
+        }
+      });
+  }
+}
index 8a41575c36fc55c88e6e26892f7956f318cf7b1f..0ad69ccf50a335f2577247d4d88073a0aa1ed61a 100644 (file)
@@ -24,7 +24,8 @@
       <a ngbNavLink
          i18n>Subvolume groups</a>
       <ng-template ngbNavContent>
-        <cd-cephfs-subvolume-group [fsName]="selection.mdsmap.fs_name">
+        <cd-cephfs-subvolume-group [fsName]="selection.mdsmap.fs_name"
+                                   [pools]="details.pools">
         </cd-cephfs-subvolume-group>
       </ng-template>
     </ng-container>
index 51ca4f96e1a8a76dad8dcbf45cf0fbaa735b450c..a83e0f16870fcef7737f0f4c389b64232041dacf 100644 (file)
@@ -18,6 +18,7 @@ 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';
+import { CephfsSubvolumegroupFormComponent } from './cephfs-subvolumegroup-form/cephfs-subvolumegroup-form.component';
 
 @NgModule({
   imports: [
@@ -43,7 +44,8 @@ import { CephfsSubvolumeGroupComponent } from './cephfs-subvolume-group/cephfs-s
     CephfsSubvolumeListComponent,
     CephfsSubvolumeFormComponent,
     CephfsDirectoriesComponent,
-    CephfsSubvolumeGroupComponent
+    CephfsSubvolumeGroupComponent,
+    CephfsSubvolumegroupFormComponent
   ]
 })
 export class CephfsModule {}
index 853041fe8b755d8c636d42ae582aa5e694ae8e0f..db7a9db91bb980b97d043cf9d1e11901bcb259ca 100644 (file)
@@ -1,7 +1,9 @@
 import { HttpClient } from '@angular/common/http';
 import { Injectable } from '@angular/core';
-import { Observable } from 'rxjs';
+import { Observable, of } from 'rxjs';
 import { CephfsSubvolumeGroup } from '../models/cephfs-subvolume-group.model';
+import _ from 'lodash';
+import { mapTo, catchError } from 'rxjs/operators';
 
 @Injectable({
   providedIn: 'root'
@@ -14,4 +16,48 @@ export class CephfsSubvolumeGroupService {
   get(volName: string): Observable<CephfsSubvolumeGroup[]> {
     return this.http.get<CephfsSubvolumeGroup[]>(`${this.baseURL}/${volName}`);
   }
+
+  create(
+    volName: string,
+    groupName: string,
+    poolName: string,
+    size: number,
+    uid: number,
+    gid: number,
+    mode: string
+  ) {
+    return this.http.post(
+      this.baseURL,
+      {
+        vol_name: volName,
+        group_name: groupName,
+        pool_layout: poolName,
+        size: size,
+        uid: uid,
+        gid: gid,
+        mode: mode
+      },
+      { observe: 'response' }
+    );
+  }
+
+  info(volName: string, groupName: string) {
+    return this.http.get(`${this.baseURL}/${volName}/info`, {
+      params: {
+        group_name: groupName
+      }
+    });
+  }
+
+  exists(groupName: string, volName: string) {
+    return this.info(volName, groupName).pipe(
+      mapTo(true),
+      catchError((error: Event) => {
+        if (_.isFunction(error.preventDefault)) {
+          error.preventDefault();
+        }
+        return of(false);
+      })
+    );
+  }
 }
index fc5b08be8bd81748d13eae18d7cb0b3821570115..f0bc2825144a3a126485be09036059208e392408 100644 (file)
@@ -358,6 +358,9 @@ export class TaskMessageService {
     ),
     'cephfs/subvolume/create': this.newTaskMessage(this.commonOperations.create, (metadata) =>
       this.subvolume(metadata)
+    ),
+    'cephfs/subvolume/group/create': this.newTaskMessage(this.commonOperations.create, (metadata) =>
+      this.subvolumegroup(metadata)
     )
   };
 
@@ -422,6 +425,10 @@ export class TaskMessageService {
     return $localize`subvolume '${metadata.subVolumeName}'`;
   }
 
+  subvolumegroup(metadata: any) {
+    return $localize`subvolume group '${metadata.subvolumegroupName}'`;
+  }
+
   crudMessageId(id: string) {
     return $localize`${id}`;
   }
index 55801683fbd7af82a8fb7a0d00434dfd189414d1..1772563ac0a1a6d996749e3f0ad716cc4058b2bb 100644 (file)
@@ -1721,6 +1721,46 @@ paths:
       - jwt: []
       tags:
       - CephFSSubvolume
+  /api/cephfs/subvolume/group:
+    post:
+      parameters: []
+      requestBody:
+        content:
+          application/json:
+            schema:
+              properties:
+                group_name:
+                  type: string
+                vol_name:
+                  type: string
+              required:
+              - vol_name
+              - group_name
+              type: object
+      responses:
+        '201':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              type: object
+          description: Resource created.
+        '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}:
     get:
       parameters: