]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: cephfs volume creation form 52526/head
authorPere Diaz Bou <pere-altea@hotmail.com>
Tue, 18 Jul 2023 19:30:17 +0000 (21:30 +0200)
committerPere Diaz Bou <pere-altea@hotmail.com>
Mon, 31 Jul 2023 08:17:46 +0000 (10:17 +0200)
Fixes: https://tracker.ceph.com/issues/62085
Signed-off-by: Pere Diaz Bou <pere-altea@hotmail.com>
14 files changed:
src/pybind/mgr/dashboard/controllers/cephfs.py
src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-directories/cephfs-directories.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts
src/pybind/mgr/dashboard/openapi.yaml

index f68c1512d9bbae7284ff59b573f0b4a0d572b528..5cfa2b5f800cb4f7c75011357fb91450a3ac5c59 100644 (file)
@@ -2,6 +2,7 @@
 import logging
 import os
 from collections import defaultdict
+from typing import Any, Dict
 
 import cephfs
 import cherrypy
@@ -28,6 +29,7 @@ GET_STATFS_SCHEMA = {
 logger = logging.getLogger("controllers.rgw")
 
 
+# pylint: disable=R0904
 @APIRouter('/cephfs', Scope.CEPHFS)
 @APIDoc("Cephfs Management API", "Cephfs")
 class CephFS(RESTController):
@@ -42,6 +44,24 @@ class CephFS(RESTController):
         fsmap = mgr.get("fs_map")
         return fsmap['filesystems']
 
+    def create(self, name: str, service_spec: Dict[str, Any]):
+        service_spec_str = '1 '
+        if 'labels' in service_spec['placement']:
+            for label in service_spec['placement']['labels']:
+                service_spec_str += f'label:{label},'
+            service_spec_str = service_spec_str[:-1]
+        if 'hosts' in service_spec['placement']:
+            for host in service_spec['placement']['hosts']:
+                service_spec_str += f'{host},'
+            service_spec_str = service_spec_str[:-1]
+
+        error_code, _, err = mgr.remote('volumes', '_cmd_fs_volume_create', None,
+                                        {'name': name, 'placement': service_spec_str})
+        if error_code != 0:
+            raise RuntimeError(
+                f'Error creating volume {name} with placement {str(service_spec)}: {err}')
+        return f'Volume {name} created successfully'
+
     def get(self, fs_id):
         fs_id = self.fs_id_to_int(fs_id)
         return self.fs_status(fs_id)
index ab21ee603d8984cd9ab36f09f894fecf41dd2c99..98d14779ddf1f9755ca0b05641c064e96a244eea 100644 (file)
@@ -46,6 +46,7 @@ import { FeatureTogglesGuardService } from './shared/services/feature-toggles-gu
 import { ModuleStatusGuardService } from './shared/services/module-status-guard.service';
 import { NoSsoGuardService } from './shared/services/no-sso-guard.service';
 import { UpgradeComponent } from './ceph/cluster/upgrade/upgrade.component';
+import { CephfsVolumeFormComponent } from './ceph/cephfs/cephfs-form/cephfs-form.component';
 
 @Injectable()
 export class PerformanceCounterBreadcrumbsResolver extends BreadcrumbsResolver {
@@ -328,9 +329,16 @@ const routes: Routes = [
       // File Systems
       {
         path: 'cephfs',
-        component: CephfsListComponent,
         canActivate: [FeatureTogglesGuardService],
-        data: { breadcrumbs: 'File Systems' }
+        data: { breadcrumbs: 'File Systems' },
+        children: [
+          { path: '', component: CephfsListComponent },
+          {
+            path: URLVerbs.CREATE,
+            component: CephfsVolumeFormComponent,
+            data: { breadcrumbs: ActionLabels.CREATE }
+          }
+        ]
       },
       // Object Gateway
       {
index 4ae8a159a0559111fa49c1f3164772733f58cfcd..841d635b1a0957c1b8d067952397b784ff3589ac 100644 (file)
@@ -208,6 +208,11 @@ export class CephfsDirectoriesComponent implements OnInit, OnChanges {
           name: $localize`Created`,
           flexGrow: 1,
           pipe: this.cdDatePipe
+        },
+        {
+          prop: 'created',
+          name: $localize`Capacity`,
+          flexGrow: 1
         }
       ],
       selection: new CdTableSelection(),
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.html
new file mode 100644 (file)
index 0000000..dc2b1fd
--- /dev/null
@@ -0,0 +1,103 @@
+<div class="cd-col-form"
+     *ngIf="orchStatus$ | async as orchStatus">
+  <form #frm="ngForm"
+        #formDir="ngForm"
+        [formGroup]="form"
+        novalidate>
+    <div class="card">
+      <div i18n="form title|Example: Create Volume@@formTitle"
+           class="card-header">{{ action | titlecase }} {{ resource | upperFirst }}</div>
+
+      <ng-container *ngIf="!orchStatus.available">
+        <cd-alert-panel type="info"
+                        class="m-3"
+                        spacingClass="mt-3"
+                        i18n>Orchestrator is not configured. Deploy MDS daemons manually after creating the volume.</cd-alert-panel>
+      </ng-container>
+      <div class="card-body">
+        <!-- Name -->
+        <div class="form-group row">
+          <label class="cd-col-form-label required"
+                 for="name"
+                 i18n>Name</label>
+          <div class="cd-col-form-input">
+            <input id="name"
+                   name="name"
+                   type="text"
+                   class="form-control"
+                   placeholder="Name..."
+                   i18n-placeholder
+                   formControlName="name"
+                   autofocus>
+            <span class="invalid-feedback"
+                  *ngIf="form.showError('name', formDir, 'required')"
+                  i18n>This field is required!</span>
+            <span *ngIf="form.showError('name', formDir, 'pattern')"
+                  class="invalid-feedback"
+                  i18n>Volume name can only contain letters, numbers, '.', '-', '_' or '/'.</span>
+          </div>
+        </div>
+
+        <ng-container *ngIf="orchStatus.available">
+          <!-- Placement -->
+          <div class="form-group row">
+            <label class="cd-col-form-label"
+                   for="placement"
+                   i18n>Placement</label>
+            <div class="cd-col-form-input">
+              <select id="placement"
+                      class="form-select"
+                      formControlName="placement">
+                <option i18n
+                        value="hosts">Hosts</option>
+                <option i18n
+                        value="label">Label</option>
+              </select>
+            </div>
+          </div>
+
+          <!-- Label -->
+          <div *ngIf="form.controls.placement.value === 'label'"
+               class="form-group row">
+            <label i18n
+                   class="cd-col-form-label"
+                   for="label">Label</label>
+            <div class="cd-col-form-input">
+              <input id="label"
+                     class="form-control"
+                     type="text"
+                     formControlName="label"
+                     [ngbTypeahead]="searchLabels"
+                     (focus)="labelFocus.next($any($event).target.value)"
+                     (click)="labelClick.next($any($event).target.value)">
+              <span class="invalid-feedback"
+                    *ngIf="form.showError('label', frm, 'required')"
+                    i18n>This field is required.</span>
+            </div>
+          </div>
+
+          <!-- Hosts -->
+          <div *ngIf="form.controls.placement.value === 'hosts'"
+               class="form-group row">
+            <label class="cd-col-form-label"
+                   for="hosts"
+                   i18n>Hosts</label>
+            <div class="cd-col-form-input">
+              <cd-select-badges id="hosts"
+                                [data]="form.controls.hosts.value"
+                                [options]="hosts.options"
+                                [messages]="hosts.messages">
+              </cd-select-badges>
+            </div>
+          </div>
+        </ng-container>
+      </div>
+      <div class="card-footer">
+        <cd-form-button-panel (submitActionEvent)="submit()"
+                              [form]="form"
+                              [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+                              wrappingClass="text-right"></cd-form-button-panel>
+      </div>
+    </div>
+  </form>
+</div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.spec.ts
new file mode 100644 (file)
index 0000000..cf85a21
--- /dev/null
@@ -0,0 +1,32 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { RouterTestingModule } from '@angular/router/testing';
+import { CephfsVolumeFormComponent } from './cephfs-form.component';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { SharedModule } from '~/app/shared/shared.module';
+import { ToastrModule } from 'ngx-toastr';
+import { ReactiveFormsModule } from '@angular/forms';
+describe('CephfsVolumeFormComponent', () => {
+  let component: CephfsVolumeFormComponent;
+  let fixture: ComponentFixture<CephfsVolumeFormComponent>;
+  configureTestBed({
+    imports: [
+      BrowserAnimationsModule,
+      SharedModule,
+      HttpClientTestingModule,
+      RouterTestingModule,
+      ReactiveFormsModule,
+      ToastrModule.forRoot()
+    ],
+    declarations: [CephfsVolumeFormComponent]
+  });
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CephfsVolumeFormComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-form/cephfs-form.component.ts
new file mode 100644 (file)
index 0000000..fd77a4c
--- /dev/null
@@ -0,0 +1,166 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import _ from 'lodash';
+
+import { NgbNav, NgbTooltip, NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
+import { merge, Observable, Subject } from 'rxjs';
+import { debounceTime, distinctUntilChanged, filter, map } from 'rxjs/operators';
+
+import { CephfsService } from '~/app/shared/api/cephfs.service';
+import { HostService } from '~/app/shared/api/host.service';
+import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
+import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
+import { SelectOption } from '~/app/shared/components/select/select-option.model';
+import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { CdForm } from '~/app/shared/forms/cd-form';
+import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { CdValidators } from '~/app/shared/forms/cd-validators';
+import { FinishedTask } from '~/app/shared/models/finished-task';
+import { Permission } from '~/app/shared/models/permissions';
+import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
+
+@Component({
+  selector: 'cd-cephfs-form',
+  templateUrl: './cephfs-form.component.html',
+  styleUrls: ['./cephfs-form.component.scss']
+})
+export class CephfsVolumeFormComponent extends CdForm implements OnInit {
+  @ViewChild('crushInfoTabs') crushInfoTabs: NgbNav;
+  @ViewChild('crushDeletionBtn') crushDeletionBtn: NgbTooltip;
+  @ViewChild('ecpInfoTabs') ecpInfoTabs: NgbNav;
+  @ViewChild('ecpDeletionBtn') ecpDeletionBtn: NgbTooltip;
+  @ViewChild(NgbTypeahead, { static: false })
+  typeahead: NgbTypeahead;
+
+  labelFocus = new Subject<string>();
+  labelClick = new Subject<string>();
+
+  orchStatus$: Observable<any>;
+
+  permission: Permission;
+  form: CdFormGroup;
+  action: string;
+  resource: string;
+  editing: boolean;
+  icons = Icons;
+  hosts: any;
+  labels: string[];
+  hasOrchestrator: boolean;
+
+  constructor(
+    private router: Router,
+    private taskWrapperService: TaskWrapperService,
+    private orchService: OrchestratorService,
+    private formBuilder: CdFormBuilder,
+    public actionLabels: ActionLabelsI18n,
+    private hostService: HostService,
+    private cephfsService: CephfsService
+  ) {
+    super();
+    this.editing = this.router.url.startsWith(`/pool/${URLVerbs.EDIT}`);
+    this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE;
+    this.resource = $localize`volume`;
+    this.hosts = {
+      options: [],
+      messages: new SelectMessages({
+        empty: $localize`There are no hosts.`,
+        filter: $localize`Filter hosts`
+      })
+    };
+    this.createForm();
+  }
+
+  private createForm() {
+    this.orchService.status().subscribe((status) => {
+      this.hasOrchestrator = status.available;
+    });
+    this.form = this.formBuilder.group({
+      name: new FormControl('', {
+        validators: [Validators.pattern(/^[.A-Za-z0-9_/-]+$/), Validators.required]
+      }),
+      placement: ['hosts'],
+      hosts: [[]],
+      label: [
+        null,
+        [
+          CdValidators.requiredIf({
+            placement: 'label',
+            unmanaged: false
+          })
+        ]
+      ],
+      unmanaged: [false]
+    });
+  }
+
+  ngOnInit() {
+    this.hostService.list('false').subscribe((resp: object[]) => {
+      const options: SelectOption[] = [];
+      _.forEach(resp, (host: object) => {
+        if (_.get(host, 'sources.orchestrator', false)) {
+          const option = new SelectOption(false, _.get(host, 'hostname'), '');
+          options.push(option);
+        }
+      });
+      this.hosts.options = [...options];
+    });
+    this.hostService.getLabels().subscribe((resp: string[]) => {
+      this.labels = resp;
+    });
+    this.orchStatus$ = this.orchService.status();
+  }
+
+  searchLabels = (text$: Observable<string>) => {
+    return merge(
+      text$.pipe(debounceTime(200), distinctUntilChanged()),
+      this.labelFocus,
+      this.labelClick.pipe(filter(() => !this.typeahead.isPopupOpen()))
+    ).pipe(
+      map((value) =>
+        this.labels
+          .filter((label: string) => label.toLowerCase().indexOf(value.toLowerCase()) > -1)
+          .slice(0, 10)
+      )
+    );
+  };
+
+  submit() {
+    let values = this.form.getRawValue();
+    const serviceSpec: object = {
+      placement: {},
+      unmanaged: values['unmanaged']
+    };
+    switch (values['placement']) {
+      case 'hosts':
+        if (values['hosts'].length > 0) {
+          serviceSpec['placement']['hosts'] = values['hosts'];
+        }
+        break;
+      case 'label':
+        serviceSpec['placement']['label'] = values['label'];
+        break;
+    }
+
+    const volumeName = this.form.get('name').value;
+    const self = this;
+    let taskUrl = `cephfs/${URLVerbs.CREATE}`;
+    this.taskWrapperService
+      .wrapTaskAroundCall({
+        task: new FinishedTask(taskUrl, {
+          volumeName: volumeName
+        }),
+        call: this.cephfsService.create(this.form.get('name').value, serviceSpec)
+      })
+      .subscribe({
+        error() {
+          self.form.setErrors({ cdSubmitButton: true });
+        },
+        complete: () => {
+          this.router.navigate(['cephfs']);
+        }
+      });
+  }
+}
index 05960e87fa1975065b17cd2621dd4f636ca7a58e..cf5c0a51c633d419f39616ab22bb1f02d6b72e7c 100644 (file)
   <cd-cephfs-tabs cdTableDetail
                   [selection]="expandedRow">
   </cd-cephfs-tabs>
+  <div class="table-actions btn-toolbar">
+    <cd-table-actions [permission]="permissions.cephfs"
+                      [selection]="selection"
+                      class="btn-group"
+                      id="cephfs-actions"
+                      [tableActions]="tableActions">
+    </cd-table-actions>
+  </div>
 </cd-table>
index 793651081dc89c683ed10f252692649f0ba69b1c..47923d5e0d3ac4afa1e4df3f4889c3191c1fad35 100644 (file)
@@ -2,10 +2,12 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { Component, Input } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { RouterTestingModule } from '@angular/router/testing';
 
 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
 import { SharedModule } from '~/app/shared/shared.module';
 import { configureTestBed } from '~/testing/unit-test-helper';
+import { CephfsVolumeFormComponent } from '../cephfs-form/cephfs-form.component';
 import { CephfsListComponent } from './cephfs-list.component';
 
 @Component({ selector: 'cd-cephfs-tabs', template: '' })
@@ -19,8 +21,8 @@ describe('CephfsListComponent', () => {
   let fixture: ComponentFixture<CephfsListComponent>;
 
   configureTestBed({
-    imports: [BrowserAnimationsModule, SharedModule, HttpClientTestingModule],
-    declarations: [CephfsListComponent, CephfsTabsStubComponent]
+    imports: [BrowserAnimationsModule, SharedModule, HttpClientTestingModule, RouterTestingModule],
+    declarations: [CephfsListComponent, CephfsTabsStubComponent, CephfsVolumeFormComponent]
   });
 
   beforeEach(() => {
index 8d19d394c3455de717aeee7ac9b09daa1468ca71..c752a9c58e4b100bb5872d7a338d0719648e0407 100644 (file)
@@ -1,25 +1,45 @@
 import { Component, OnInit } from '@angular/core';
+import { Permissions } from '~/app/shared/models/permissions';
+import { Router } from '@angular/router';
 
 import { CephfsService } from '~/app/shared/api/cephfs.service';
 import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
 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 { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { URLBuilderService } from '~/app/shared/services/url-builder.service';
+
+const BASE_URL = 'cephfs';
 
 @Component({
   selector: 'cd-cephfs-list',
   templateUrl: './cephfs-list.component.html',
-  styleUrls: ['./cephfs-list.component.scss']
+  styleUrls: ['./cephfs-list.component.scss'],
+  providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
 })
 export class CephfsListComponent extends ListWithDetails implements OnInit {
   columns: CdTableColumn[];
   filesystems: any = [];
   selection = new CdTableSelection();
+  tableActions: CdTableAction[];
+  permissions: Permissions;
 
-  constructor(private cephfsService: CephfsService, private cdDatePipe: CdDatePipe) {
+  constructor(
+    private authStorageService: AuthStorageService,
+    private cephfsService: CephfsService,
+    private cdDatePipe: CdDatePipe,
+    public actionLabels: ActionLabelsI18n,
+    private router: Router,
+    private urlBuilder: URLBuilderService
+  ) {
     super();
+    this.permissions = this.authStorageService.getPermissions();
   }
 
   ngOnInit() {
@@ -42,6 +62,15 @@ export class CephfsListComponent extends ListWithDetails implements OnInit {
         cellTransformation: CellTemplate.checkIcon
       }
     ];
+    this.tableActions = [
+      {
+        name: this.actionLabels.CREATE,
+        permission: 'create',
+        icon: Icons.add,
+        click: () => this.router.navigate([this.urlBuilder.getCreate()]),
+        canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
+      }
+    ];
   }
 
   loadFilesystems(context: CdTableFetchDataContext) {
index 41b58a0a36b124e14fa2d499175a39cbef99fa11..31398666c53cf51dfa40df4f41c15842f190e4c0 100644 (file)
@@ -1,8 +1,9 @@
 import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 
 import { TreeModule } from '@circlon/angular-tree-component';
-import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgbNavModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
 import { ChartsModule } from 'ng2-charts';
 
 import { AppRoutingModule } from '~/app/app-routing.module';
@@ -11,17 +12,29 @@ import { CephfsChartComponent } from './cephfs-chart/cephfs-chart.component';
 import { CephfsClientsComponent } from './cephfs-clients/cephfs-clients.component';
 import { CephfsDetailComponent } from './cephfs-detail/cephfs-detail.component';
 import { CephfsDirectoriesComponent } from './cephfs-directories/cephfs-directories.component';
+import { CephfsVolumeFormComponent } from './cephfs-form/cephfs-form.component';
 import { CephfsListComponent } from './cephfs-list/cephfs-list.component';
 import { CephfsTabsComponent } from './cephfs-tabs/cephfs-tabs.component';
 
 @NgModule({
-  imports: [CommonModule, SharedModule, AppRoutingModule, ChartsModule, TreeModule, NgbNavModule],
+  imports: [
+    CommonModule,
+    SharedModule,
+    AppRoutingModule,
+    ChartsModule,
+    TreeModule,
+    NgbNavModule,
+    FormsModule,
+    ReactiveFormsModule,
+    NgbTypeaheadModule
+  ],
   declarations: [
     CephfsDetailComponent,
     CephfsClientsComponent,
     CephfsChartComponent,
     CephfsListComponent,
     CephfsTabsComponent,
+    CephfsVolumeFormComponent,
     CephfsDirectoriesComponent
   ]
 })
index 02f31ca7b56b82ac45f899efe289aa7c9d28889e..4e212adeba994d566d6552a984a195b6b8b983ea 100644 (file)
@@ -73,4 +73,14 @@ export class CephfsService {
       params
     });
   }
+
+  create(name: string, serviceSpec: object) {
+    return this.http.post(
+      this.baseURL,
+      { name: name, service_spec: serviceSpec },
+      {
+        observe: 'response'
+      }
+    );
+  }
 }
index bc11a0be39cac07b4d5dc7ce9a9deb38f2e2d98c..bf8f189b4e497a1c81660ce591d818abe2a6fb2b 100644 (file)
@@ -352,6 +352,9 @@ export class TaskMessageService {
     ),
     'crud-component/id': this.newTaskMessage(this.commonOperations.delete, (id) =>
       this.crudMessageId(id)
+    ),
+    'cephfs/create': this.newTaskMessage(this.commonOperations.create, (metadata) =>
+      this.volume(metadata)
     )
   };
 
@@ -408,6 +411,10 @@ export class TaskMessageService {
     return $localize`${message}`;
   }
 
+  volume(metadata: any) {
+    return $localize`'${metadata.volumeName}'`;
+  }
+
   crudMessageId(id: string) {
     return $localize`${id}`;
   }
index 3d1929c741a79d1c558222a62578f6f6418e6b27..fb8e3df8ef0dc82c4b7d8aaed4f149344a8c6500 100644 (file)
@@ -1642,6 +1642,45 @@ paths:
       - jwt: []
       tags:
       - Cephfs
+    post:
+      parameters: []
+      requestBody:
+        content:
+          application/json:
+            schema:
+              properties:
+                name:
+                  type: string
+                service_spec:
+                  type: string
+              required:
+              - name
+              - service_spec
+              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:
+      - Cephfs
   /api/cephfs/{fs_id}:
     get:
       parameters: