]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/blob
8c837d3885dd1b0c5698fa1702896a95a8eed69d
[ceph-ci.git] /
1 import { Component, OnInit } from '@angular/core';
2 import { UntypedFormControl, Validators } from '@angular/forms';
3 import { ActivatedRoute, Router } from '@angular/router';
4 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
5 import {
6   NamespaceCreateRequest,
7   NamespaceUpdateRequest,
8   NvmeofService
9 } from '~/app/shared/api/nvmeof.service';
10 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
11 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
12 import { FinishedTask } from '~/app/shared/models/finished-task';
13 import { NvmeofSubsystemNamespace } from '~/app/shared/models/nvmeof';
14 import { Permission } from '~/app/shared/models/permissions';
15 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
16 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
17 import { Pool } from '../../pool/pool';
18 import { PoolService } from '~/app/shared/api/pool.service';
19 import { RbdService } from '~/app/shared/api/rbd.service';
20 import { FormatterService } from '~/app/shared/services/formatter.service';
21 import { forkJoin, Observable } from 'rxjs';
22 import { CdValidators } from '~/app/shared/forms/cd-validators';
23 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
24 import { HttpResponse } from '@angular/common/http';
25
26 @Component({
27   selector: 'cd-nvmeof-namespaces-form',
28   templateUrl: './nvmeof-namespaces-form.component.html',
29   styleUrls: ['./nvmeof-namespaces-form.component.scss'],
30   standalone: false
31 })
32 export class NvmeofNamespacesFormComponent implements OnInit {
33   action: string;
34   permission: Permission;
35   poolPermission: Permission;
36   resource: string;
37   pageURL: string;
38   edit: boolean = false;
39   nsForm: CdFormGroup;
40   subsystemNQN: string;
41   rbdPools: Array<Pool> = null;
42   units: Array<string> = ['MiB', 'GiB', 'TiB'];
43   nsid: string;
44   currentBytes: number;
45   invalidSizeError: boolean;
46   group: string;
47   MAX_NAMESPACE_CREATE: number = 5;
48   MIN_NAMESPACE_CREATE: number = 1;
49   requiredInvalidText: string = $localize`This field is required`;
50   nsCountInvalidText: string = $localize`The namespace count should be between 1 and 5`;
51
52   constructor(
53     public actionLabels: ActionLabelsI18n,
54     private authStorageService: AuthStorageService,
55     private taskWrapperService: TaskWrapperService,
56     private nvmeofService: NvmeofService,
57     private poolService: PoolService,
58     private rbdService: RbdService,
59     private router: Router,
60     private route: ActivatedRoute,
61     public activeModal: NgbActiveModal,
62     public formatterService: FormatterService,
63     public dimlessBinaryPipe: DimlessBinaryPipe
64   ) {
65     this.permission = this.authStorageService.getPermissions().nvmeof;
66     this.poolPermission = this.authStorageService.getPermissions().pool;
67     this.resource = $localize`Namespace`;
68     this.pageURL = 'block/nvmeof/subsystems';
69   }
70
71   init() {
72     this.route.queryParams.subscribe((params) => {
73       this.group = params?.['group'];
74     });
75     this.createForm();
76     this.action = this.actionLabels.CREATE;
77     this.route.params.subscribe((params: { subsystem_nqn: string; nsid: string }) => {
78       this.subsystemNQN = params.subsystem_nqn;
79       this.nsid = params?.nsid;
80     });
81   }
82
83   initForEdit() {
84     this.edit = true;
85     this.action = this.actionLabels.EDIT;
86     this.nvmeofService
87       .getNamespace(this.subsystemNQN, this.nsid, this.group)
88       .subscribe((res: NvmeofSubsystemNamespace) => {
89         const convertedSize = this.dimlessBinaryPipe.transform(res.rbd_image_size).split(' ');
90         this.currentBytes =
91           typeof res.rbd_image_size === 'string' ? Number(res.rbd_image_size) : res.rbd_image_size;
92         this.nsForm.get('pool').setValue(res.rbd_pool_name);
93         this.nsForm.get('unit').setValue(convertedSize[1]);
94         this.nsForm.get('image_size').setValue(convertedSize[0]);
95         this.nsForm.get('image_size').addValidators(Validators.required);
96         this.nsForm.get('pool').disable();
97       });
98   }
99
100   initForCreate() {
101     this.poolService.getList().subscribe((resp: Pool[]) => {
102       this.rbdPools = resp.filter(this.rbdService.isRBDPool);
103       if (this.rbdPools?.length) {
104         this.nsForm.get('pool').setValue(this.rbdPools[0].pool_name);
105       }
106     });
107   }
108
109   ngOnInit() {
110     this.init();
111     if (this.router.url.includes('subsystems/(modal:edit')) {
112       this.initForEdit();
113     } else {
114       this.initForCreate();
115     }
116   }
117
118   createForm() {
119     this.nsForm = new CdFormGroup({
120       pool: new UntypedFormControl(null, {
121         validators: [Validators.required]
122       }),
123       image_size: new UntypedFormControl(1, [CdValidators.number(false), Validators.min(1)]),
124       unit: new UntypedFormControl(this.units[1]),
125       nsCount: new UntypedFormControl(this.MAX_NAMESPACE_CREATE, [
126         Validators.required,
127         Validators.max(this.MAX_NAMESPACE_CREATE),
128         Validators.min(this.MIN_NAMESPACE_CREATE)
129       ])
130     });
131   }
132
133   buildUpdateRequest(rbdImageSize: number): Observable<HttpResponse<Object>> {
134     const request: NamespaceUpdateRequest = {
135       gw_group: this.group,
136       rbd_image_size: rbdImageSize
137     };
138     return this.nvmeofService.updateNamespace(
139       this.subsystemNQN,
140       this.nsid,
141       request as NamespaceUpdateRequest
142     );
143   }
144
145   randomString() {
146     return Math.random().toString(36).substring(2);
147   }
148
149   buildCreateRequest(rbdImageSize: number, nsCount: number): Observable<HttpResponse<Object>>[] {
150     const pool = this.nsForm.getValue('pool');
151     const requests: Observable<HttpResponse<Object>>[] = [];
152
153     for (let i = 1; i <= nsCount; i++) {
154       const request: NamespaceCreateRequest = {
155         gw_group: this.group,
156         rbd_image_name: `nvme_${pool}_${this.group}_${this.randomString()}`,
157         rbd_pool: pool,
158         create_image: true
159       };
160       if (rbdImageSize) {
161         request['rbd_image_size'] = rbdImageSize;
162       }
163       requests.push(this.nvmeofService.createNamespace(this.subsystemNQN, request));
164     }
165
166     return requests;
167   }
168
169   validateSize() {
170     const unit = this.nsForm.getValue('unit');
171     const image_size = this.nsForm.getValue('image_size');
172     if (image_size && unit) {
173       const bytes = this.formatterService.toBytes(image_size + unit);
174       return bytes <= this.currentBytes;
175     }
176     return null;
177   }
178
179   onSubmit() {
180     if (this.validateSize()) {
181       this.invalidSizeError = true;
182       this.nsForm.setErrors({ cdSubmitButton: true });
183     } else {
184       this.invalidSizeError = false;
185       const component = this;
186       const taskUrl: string = `nvmeof/namespace/${this.edit ? URLVerbs.EDIT : URLVerbs.CREATE}`;
187       const image_size = this.nsForm.getValue('image_size');
188       const nsCount = this.nsForm.getValue('nsCount');
189       let action: Observable<HttpResponse<Object>>;
190       let rbdImageSize: number = null;
191
192       if (image_size) {
193         const image_size_unit = this.nsForm.getValue('unit');
194         const value: number = this.formatterService.toBytes(image_size + image_size_unit);
195         rbdImageSize = value;
196       }
197       if (this.edit) {
198         action = this.taskWrapperService.wrapTaskAroundCall({
199           task: new FinishedTask(taskUrl, {
200             nqn: this.subsystemNQN,
201             nsid: this.nsid
202           }),
203           call: this.buildUpdateRequest(rbdImageSize)
204         });
205       } else {
206         action = this.taskWrapperService.wrapTaskAroundCall({
207           task: new FinishedTask(taskUrl, {
208             nqn: this.subsystemNQN,
209             nsCount
210           }),
211           call: forkJoin(this.buildCreateRequest(rbdImageSize, nsCount))
212         });
213       }
214
215       action.subscribe({
216         error() {
217           component.nsForm.setErrors({ cdSubmitButton: true });
218         },
219         complete: () => {
220           this.router.navigate([this.pageURL, { outlets: { modal: null } }]);
221         }
222       });
223     }
224   }
225 }