(click)="createMultisiteSetup()">
Click here</a> to create a new Realm/Zone Group/Zone
</cd-alert-panel>
+
<!-- Service type -->
<div class="form-group row">
<label class="cd-col-form-label required"
name="service_type"
class="form-select"
formControlName="service_type"
- (change)="getServiceIds($event.target.value)">
+ (change)="onServiceTypeChange($event.target.value)">
<option i18n
[ngValue]="null">-- Select a service type --</option>
<option *ngFor="let serviceType of serviceTypes"
</div>
<!-- backend_service -->
- <div *ngIf="serviceForm.controls.service_type.value === 'ingress'"
- class="form-group row">
- <label i18n
- class="cd-col-form-label"
- [ngClass]="{'required': ['ingress'].includes(serviceForm.controls.service_type.value)}"
- for="backend_service">Backend Service</label>
- <div class="cd-col-form-input">
- <select id="backend_service"
- name="backend_service"
- class="form-select"
- formControlName="backend_service"
- (change)="prePopulateId()">
- <option *ngIf="services === null"
- [ngValue]="null"
- i18n>Loading...</option>
- <option *ngIf="services !== null && services.length === 0"
- [ngValue]="null"
- i18n>-- No service available --</option>
- <option *ngIf="services !== null && services.length > 0"
- [ngValue]="null"
- i18n>-- Select an existing service --</option>
- <option *ngFor="let service of services"
- [value]="service.service_name">{{ service.service_name }}</option>
- </select>
- <span class="invalid-feedback"
- *ngIf="serviceForm.showError('backend_service', frm, 'required')"
- i18n>This field is required.</span>
- </div>
+ <div *ngIf="serviceForm.controls.service_type.value === 'ingress'"
+ class="form-group row">
+ <label i18n
+ class="cd-col-form-label"
+ [ngClass]="{'required': ['ingress'].includes(serviceForm.controls.service_type.value)}"
+ for="backend_service">Backend Service</label>
+ <div class="cd-col-form-input">
+ <select id="backend_service"
+ name="backend_service"
+ class="form-select"
+ formControlName="backend_service"
+ (change)="prePopulateId()">
+ <option *ngIf="services === null"
+ [ngValue]="null"
+ i18n>Loading...</option>
+ <option *ngIf="services !== null && services.length === 0"
+ [ngValue]="null"
+ i18n>-- No service available --</option>
+ <option *ngIf="services !== null && services.length > 0"
+ [ngValue]="null"
+ i18n>-- Select an existing service --</option>
+ <option *ngFor="let service of services"
+ [value]="service.service_name">{{ service.service_name }}</option>
+ </select>
+ <span class="invalid-feedback"
+ *ngIf="serviceForm.showError('backend_service', frm, 'required')"
+ i18n>This field is required.</span>
</div>
+ </div>
+
+ <!-- NVMe/TCP -->
+ <!-- Block Pool -->
+ <div class="form-group row"
+ *ngIf="serviceForm.controls.service_type.value === 'nvmeof'">
+ <label i18n
+ class="cd-col-form-label required"
+ for="pool">Block Pool</label>
+ <div class="cd-col-form-input">
+ <select id="pool"
+ name="pool"
+ class="form-select"
+ formControlName="pool"
+ (change)="onBlockPoolChange()">
+ <option *ngIf="rbdPools === null"
+ [ngValue]="null"
+ i18n>Loading...</option>
+ <option *ngIf="rbdPools && rbdPools.length === 0"
+ [ngValue]="null"
+ i18n>-- No block pools available --</option>
+ <option *ngIf="rbdPools && rbdPools.length > 0"
+ [ngValue]="null"
+ i18n>-- Select a pool --</option>
+ <option *ngFor="let pool of rbdPools"
+ [value]="pool.pool_name">{{ pool.pool_name }}</option>
+ </select>
+ <cd-help-text i18n>
+ A pool in which the gateway configuration can be managed.
+ </cd-help-text>
+ <span class="invalid-feedback"
+ *ngIf="serviceForm.showError('pool', frm, 'required')"
+ i18n>This field is required.</span>
+ </div>
+ </div>
<!-- Service id -->
<div class="form-group row"
*ngIf="serviceForm.controls.service_type.value !== 'snmp-gateway'">
<label class="cd-col-form-label"
- [ngClass]="{'required': ['mds', 'rgw', 'nfs', 'iscsi', 'smb', 'ingress'].includes(serviceForm.controls.service_type.value)}"
+ [ngClass]="{'required': ['mds', 'rgw', 'nfs', 'iscsi', 'nvmeof', 'smb', 'ingress'].includes(serviceForm.controls.service_type.value)}"
for="service_id">
- <span i18n>Id</span>
- <cd-helper i18n>Used in the service name which is <service_type.service_id></cd-helper>
+ <span i18n>Service Name</span>
</label>
<div class="cd-col-form-input">
- <input id="service_id"
- class="form-control"
- type="text"
- formControlName="service_id">
+ <div class="input-group">
+ <span class="input-group-text"
+ *ngIf="serviceForm.controls.service_type.value && ['mds', 'rgw', 'nfs', 'iscsi', 'nvmeof', 'smb', 'ingress'].includes(serviceForm.controls.service_type.value)"
+ for="userId"
+ i18n>{{serviceForm.controls.service_type.value}}.
+ </span>
+ <input id="service_id"
+ class="form-control"
+ type="text"
+ formControlName="service_id">
+ </div>
<span class="invalid-feedback"
*ngIf="serviceForm.showError('service_id', frm, 'required')"
i18n>This field is required.</span>
id="unmanaged"
type="checkbox"
formControlName="unmanaged">
- <label class="custom-control-label"
+ <label class="custom-control-label m-0"
for="unmanaged"
i18n>Unmanaged</label>
- <cd-helper i18n>If set to true, the orchestrator will not start nor stop any daemon associated with this service.
- Placement and all other properties will be ignored.</cd-helper>
+ <cd-help-text i18n>If Unmanaged is selected, the orchestrator will not stop or stop any daemons associated with this service. Placement and all other properties will be ignored.</cd-help-text>
</div>
</div>
</div>
</div>
</div>
- <!-- count -->
+ <!-- Count -->
<div *ngIf="!serviceForm.controls.unmanaged.value"
class="form-group row">
<label class="cd-col-form-label"
for="count">
<span i18n>Count</span>
- <cd-helper i18n>Only that number of daemons will be created.</cd-helper>
</label>
<div class="cd-col-form-input">
<input id="count"
type="number"
formControlName="count"
min="1">
+ <cd-help-text i18n>Number of deamons that will be deployed</cd-help-text>
<span class="invalid-feedback"
*ngIf="serviceForm.showError('count', frm, 'min')"
i18n>The value must be at least 1.</span>
import _ from 'lodash';
import { forkJoin, merge, Observable, Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, filter, map } from 'rxjs/operators';
+import { Pool } from '~/app/ceph/pool/pool';
import { CreateRgwServiceEntitiesComponent } from '~/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component';
import { RgwRealm, RgwZonegroup, RgwZone } from '~/app/ceph/rgw/models/rgw-multisite';
import { CephServiceService } from '~/app/shared/api/ceph-service.service';
import { HostService } from '~/app/shared/api/host.service';
import { PoolService } from '~/app/shared/api/pool.service';
+import { RbdService } from '~/app/shared/api/rbd.service';
import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
labels: string[];
labelClick = new Subject<string>();
labelFocus = new Subject<string>();
- pools: Array<object>;
+ pools: Array<Pool>;
+ rbdPools: Array<Pool>;
services: Array<CephServiceSpec> = [];
pageURL: string;
serviceList: CephServiceSpec[];
private formBuilder: CdFormBuilder,
private hostService: HostService,
private poolService: PoolService,
+ private rbdService: RbdService,
private router: Router,
private taskWrapperService: TaskWrapperService,
public timerService: TimerService,
CdValidators.requiredIf({
service_type: 'iscsi'
}),
+ CdValidators.requiredIf({
+ service_type: 'nvmeof'
+ }),
CdValidators.requiredIf({
service_type: 'ingress'
}),
count: [null, [CdValidators.number(false)]],
unmanaged: [false],
// iSCSI
+ // NVMe/TCP
pool: [
null,
[
CdValidators.requiredIf({
service_type: 'iscsi'
+ }),
+ CdValidators.requiredIf({
+ service_type: 'nvmeof'
})
]
],
this.hostService.getLabels().subscribe((resp: string[]) => {
this.labels = resp;
});
- this.poolService.getList().subscribe((resp: Array<object>) => {
+ this.poolService.getList().subscribe((resp: Pool[]) => {
this.pools = resp;
+ this.rbdPools = this.pools.filter(this.rbdService.isRBDPool);
});
if (this.editing) {
this.serviceForm.get('ssl_key').setValue(response[0].spec?.ssl_key);
}
break;
+ case 'nvmeof':
+ this.serviceForm.get('pool').setValue(response[0].spec.pool);
+ break;
case 'rgw':
this.serviceForm
.get('rgw_frontend_port')
.setValue(response[0].spec?.rgw_frontend_port);
- this.getServiceIds(
- 'rgw',
+ this.setRgwFields(
response[0].spec?.rgw_realm,
response[0].spec?.rgw_zonegroup,
response[0].spec?.rgw_zone
}
}
- getDefaultsEntities(
+ getDefaultsEntitiesForRgw(
defaultRealmId: string,
defaultZonegroupId: string,
defaultZoneId: string
};
}
- getServiceIds(
- selectedServiceType: string,
- realm_name?: string,
- zonegroup_name?: string,
- zone_name?: string
- ) {
+ getDefaultPlacementCount(serviceType: string) {
+ /**
+ * `defaults` from src/pybind/mgr/cephadm/module.py
+ */
+ switch (serviceType) {
+ case 'mon':
+ this.serviceForm.get('count').setValue(5);
+ break;
+ case 'mgr':
+ case 'mds':
+ case 'rgw':
+ case 'ingress':
+ case 'rbd-mirror':
+ this.serviceForm.get('count').setValue(2);
+ break;
+ case 'iscsi':
+ case 'nvmeof':
+ case 'cephfs-mirror':
+ case 'nfs':
+ case 'grafana':
+ case 'alertmanager':
+ case 'prometheus':
+ case 'loki':
+ case 'container':
+ case 'snmp-gateway':
+ case 'elastic-serach':
+ case 'jaeger-collector':
+ case 'jaeger-query':
+ case 'smb':
+ this.serviceForm.get('count').setValue(1);
+ break;
+ }
+ }
+
+ setRgwFields(realm_name?: string, zonegroup_name?: string, zone_name?: string) {
+ const observables = [
+ this.rgwRealmService.getAllRealmsInfo(),
+ this.rgwZonegroupService.getAllZonegroupsInfo(),
+ this.rgwZoneService.getAllZonesInfo()
+ ];
+ this.sub = forkJoin(observables).subscribe(
+ (multisiteInfo: [object, object, object]) => {
+ this.multisiteInfo = multisiteInfo;
+ this.realmList =
+ this.multisiteInfo[0] !== undefined && this.multisiteInfo[0].hasOwnProperty('realms')
+ ? this.multisiteInfo[0]['realms']
+ : [];
+ this.zonegroupList =
+ this.multisiteInfo[1] !== undefined && this.multisiteInfo[1].hasOwnProperty('zonegroups')
+ ? this.multisiteInfo[1]['zonegroups']
+ : [];
+ this.zoneList =
+ this.multisiteInfo[2] !== undefined && this.multisiteInfo[2].hasOwnProperty('zones')
+ ? this.multisiteInfo[2]['zones']
+ : [];
+ this.realmNames = this.realmList.map((realm) => {
+ return realm['name'];
+ });
+ this.zonegroupNames = this.zonegroupList.map((zonegroup) => {
+ return zonegroup['name'];
+ });
+ this.zoneNames = this.zoneList.map((zone) => {
+ return zone['name'];
+ });
+ this.defaultRealmId = multisiteInfo[0]['default_realm'];
+ this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
+ this.defaultZoneId = multisiteInfo[2]['default_zone'];
+ this.defaultsInfo = this.getDefaultsEntitiesForRgw(
+ this.defaultRealmId,
+ this.defaultZonegroupId,
+ this.defaultZoneId
+ );
+ if (!this.editing) {
+ this.serviceForm.get('realm_name').setValue(this.defaultsInfo['defaultRealmName']);
+ this.serviceForm
+ .get('zonegroup_name')
+ .setValue(this.defaultsInfo['defaultZonegroupName']);
+ this.serviceForm.get('zone_name').setValue(this.defaultsInfo['defaultZoneName']);
+ } else {
+ if (realm_name && !this.realmNames.includes(realm_name)) {
+ const realm = new RgwRealm();
+ realm.name = realm_name;
+ this.realmList.push(realm);
+ }
+ if (zonegroup_name && !this.zonegroupNames.includes(zonegroup_name)) {
+ const zonegroup = new RgwZonegroup();
+ zonegroup.name = zonegroup_name;
+ this.zonegroupList.push(zonegroup);
+ }
+ if (zone_name && !this.zoneNames.includes(zone_name)) {
+ const zone = new RgwZone();
+ zone.name = zone_name;
+ this.zoneList.push(zone);
+ }
+ if (zonegroup_name === undefined && zone_name === undefined) {
+ zonegroup_name = 'default';
+ zone_name = 'default';
+ }
+ this.serviceForm.get('realm_name').setValue(realm_name);
+ this.serviceForm.get('zonegroup_name').setValue(zonegroup_name);
+ this.serviceForm.get('zone_name').setValue(zone_name);
+ }
+ if (this.realmList.length === 0) {
+ this.showRealmCreationForm = true;
+ } else {
+ this.showRealmCreationForm = false;
+ }
+ },
+ (_error) => {
+ const defaultZone = new RgwZone();
+ defaultZone.name = 'default';
+ const defaultZonegroup = new RgwZonegroup();
+ defaultZonegroup.name = 'default';
+ this.zoneList.push(defaultZone);
+ this.zonegroupList.push(defaultZonegroup);
+ }
+ );
+ }
+
+ setNvmeofServiceId(): void {
+ const defaultRbdPool: string = this.rbdPools.find((p: Pool) => p.pool_name === 'rbd')
+ ?.pool_name;
+ if (defaultRbdPool) {
+ this.serviceForm.get('pool').setValue(defaultRbdPool);
+ this.serviceForm.get('service_id').setValue(defaultRbdPool);
+ }
+ }
+
+ requiresServiceId(serviceType: string) {
+ return ['mds', 'rgw', 'nfs', 'iscsi', 'nvmeof', 'smb', 'ingress'].includes(serviceType);
+ }
+
+ setServiceId(serviceId: string): void {
+ const requiresServiceId: boolean = this.requiresServiceId(serviceId);
+ if (requiresServiceId && serviceId === 'nvmeof') {
+ this.setNvmeofServiceId();
+ } else if (requiresServiceId) {
+ this.serviceForm.get('service_id').setValue(null);
+ } else {
+ this.serviceForm.get('service_id').setValue(serviceId);
+ }
+ }
+
+ onServiceTypeChange(selectedServiceType: string) {
+ this.setServiceId(selectedServiceType);
+
this.serviceIds = this.serviceList
?.filter((service) => service['service_type'] === selectedServiceType)
.map((service) => service['service_id']);
+ this.getDefaultPlacementCount(selectedServiceType);
+
if (selectedServiceType === 'rgw') {
- const observables = [
- this.rgwRealmService.getAllRealmsInfo(),
- this.rgwZonegroupService.getAllZonegroupsInfo(),
- this.rgwZoneService.getAllZonesInfo()
- ];
- this.sub = forkJoin(observables).subscribe(
- (multisiteInfo: [object, object, object]) => {
- this.multisiteInfo = multisiteInfo;
- this.realmList =
- this.multisiteInfo[0] !== undefined && this.multisiteInfo[0].hasOwnProperty('realms')
- ? this.multisiteInfo[0]['realms']
- : [];
- this.zonegroupList =
- this.multisiteInfo[1] !== undefined &&
- this.multisiteInfo[1].hasOwnProperty('zonegroups')
- ? this.multisiteInfo[1]['zonegroups']
- : [];
- this.zoneList =
- this.multisiteInfo[2] !== undefined && this.multisiteInfo[2].hasOwnProperty('zones')
- ? this.multisiteInfo[2]['zones']
- : [];
- this.realmNames = this.realmList.map((realm) => {
- return realm['name'];
- });
- this.zonegroupNames = this.zonegroupList.map((zonegroup) => {
- return zonegroup['name'];
- });
- this.zoneNames = this.zoneList.map((zone) => {
- return zone['name'];
- });
- this.defaultRealmId = multisiteInfo[0]['default_realm'];
- this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
- this.defaultZoneId = multisiteInfo[2]['default_zone'];
- this.defaultsInfo = this.getDefaultsEntities(
- this.defaultRealmId,
- this.defaultZonegroupId,
- this.defaultZoneId
- );
- if (!this.editing) {
- this.serviceForm.get('realm_name').setValue(this.defaultsInfo['defaultRealmName']);
- this.serviceForm
- .get('zonegroup_name')
- .setValue(this.defaultsInfo['defaultZonegroupName']);
- this.serviceForm.get('zone_name').setValue(this.defaultsInfo['defaultZoneName']);
- } else {
- if (realm_name && !this.realmNames.includes(realm_name)) {
- const realm = new RgwRealm();
- realm.name = realm_name;
- this.realmList.push(realm);
- }
- if (zonegroup_name && !this.zonegroupNames.includes(zonegroup_name)) {
- const zonegroup = new RgwZonegroup();
- zonegroup.name = zonegroup_name;
- this.zonegroupList.push(zonegroup);
- }
- if (zone_name && !this.zoneNames.includes(zone_name)) {
- const zone = new RgwZone();
- zone.name = zone_name;
- this.zoneList.push(zone);
- }
- if (zonegroup_name === undefined && zone_name === undefined) {
- zonegroup_name = 'default';
- zone_name = 'default';
- }
- this.serviceForm.get('realm_name').setValue(realm_name);
- this.serviceForm.get('zonegroup_name').setValue(zonegroup_name);
- this.serviceForm.get('zone_name').setValue(zone_name);
- }
- if (this.realmList.length === 0) {
- this.showRealmCreationForm = true;
- } else {
- this.showRealmCreationForm = false;
- }
- },
- (_error) => {
- const defaultZone = new RgwZone();
- defaultZone.name = 'default';
- const defaultZonegroup = new RgwZonegroup();
- defaultZonegroup.name = 'default';
- this.zoneList.push(defaultZone);
- this.zonegroupList.push(defaultZonegroup);
- }
- );
+ this.setRgwFields();
+ }
+ }
+
+ onBlockPoolChange() {
+ const selectedBlockPool = this.serviceForm.get('pool').value;
+ if (selectedBlockPool) {
+ this.serviceForm.get('service_id').setValue(selectedBlockPool);
+ } else {
+ this.serviceForm.get('service_id').setValue(null);
}
}
switch (serviceType) {
case 'ingress':
this.serviceForm.get('backend_service').disable();
+ break;
+ case 'nvmeof':
+ this.serviceForm.get('pool').disable();
+ break;
}
}
placement: {},
unmanaged: values['unmanaged']
};
- let svcId: string;
if (serviceType === 'rgw') {
serviceSpec['rgw_realm'] = values['realm_name'] ? values['realm_name'] : null;
serviceSpec['rgw_zonegroup'] =
values['zonegroup_name'] !== 'default' ? values['zonegroup_name'] : null;
serviceSpec['rgw_zone'] = values['zone_name'] !== 'default' ? values['zone_name'] : null;
- svcId = values['service_id'];
- } else {
- svcId = values['service_id'];
}
- const serviceId: string = svcId;
+
+ const serviceId: string = values['service_id'];
let serviceName: string = serviceType;
- if (_.isString(serviceId) && !_.isEmpty(serviceId)) {
+ if (_.isString(serviceId) && !_.isEmpty(serviceId) && serviceId !== serviceType) {
serviceName = `${serviceType}.${serviceId}`;
serviceSpec['service_id'] = serviceId;
}
}
break;
+ case 'nvmeof':
case 'iscsi':
serviceSpec['pool'] = values['pool'];
break;
size: 'lg'
});
this.bsModalRef.componentInstance.submitAction.subscribe(() => {
- this.getServiceIds('rgw');
+ this.setRgwFields();
});
}
}