except NoRgwDaemonsException as e:
raise DashboardException(e, http_status_code=404, component='rgw')
+ @allow_empty_body
+ # pylint: disable=W0613,W0102
+ def set(self, zonegroup_name: str, realm_name: str, new_zonegroup_name: str, default: str = '',
+ master: str = '', zonegroup_endpoints: List[str] = [], add_zones: List[str] = [],
+ remove_zones: List[str] = [], placement_targets: List[Dict[str, str]] = [],
+ daemon_name=None):
+ try:
+ instance = RgwClient.admin_instance()
+ result = instance.edit_zonegroup(realm_name, zonegroup_name, new_zonegroup_name,
+ default, master, zonegroup_endpoints, add_zones,
+ remove_zones, placement_targets)
+ return result
+ except NoRgwDaemonsException as e:
+ raise DashboardException(e, http_status_code=404, component='rgw')
+
@APIRouter('/rgw/zone', Scope.RGW)
class RgwZone(RESTController):
*ngIf="node.data.is_default">
default
</span>
- <span class="badge badge-info me-2"
+ <span class="badge badge-warning me-2"
*ngIf="node.data.is_master">
master
</span>
<div class="btn-group align-inline-btns"
- *ngIf="node.isFocused && node.data.type === 'realm'"
+ *ngIf="node.isFocused"
+ [title]="title"
role="group">
<button type="button"
- title="Edit realm"
class="btn btn-light dropdown-toggle-split ms-1"
(click)="openModal(node, true)"
+ [disabled]="getDisable()"
ngbDropdownToggle>
<i [ngClass]="[icons.edit]"></i>
</button>
.align-inline-btns {
margin-left: 5em;
}
+
+.btn:disabled {
+ pointer-events: none;
+}
defaultZoneId = '';
multisiteInfo: object[] = [];
defaultsInfo: string[] = [];
+ title: string = 'Edit';
constructor(
private modalService: ModalService,
}
getDisable() {
+ let isMasterZone = true;
if (this.defaultRealmId === '') {
return this.messages.noDefaultRealm;
} else {
- let isMasterZone = true;
this.zonegroups.forEach((zgp: any) => {
if (_.isEmpty(zgp.master_zone)) {
isMasterZone = false;
}
});
if (!isMasterZone) {
+ this.title =
+ 'Please create a master zone for each existing zonegroup to enable this feature';
return this.messages.noMasterZone;
} else {
+ this.title = 'Edit';
return false;
}
}
type="checkbox">
<label class="form-check-label"
for="default_zonegroup"
- i18n>Default</label><br>
+ i18n>Default</label>
+ <span *ngIf="disableDefault">
+ <cd-helper i18n>Zonegroup doesn't belong to the default realm.</cd-helper>
+ </span><br>
<input class="form-check-input"
id="master_zonegroup"
name="master_zonegroup"
Can be modified later by editing a zonegroup.
</cd-helper>
</span>
+ <span *ngIf="disableMaster">
+ <cd-helper i18n>Multiple master zonegroups can't be configured. If you want to create a new zonegroup and make it the master zonegroup, you must delete the default zonegroup.</cd-helper>
+ </span>
</div>
</div>
</div>
i18n>Please enter a valid IP address.</span>
</div>
</div>
+ <div class="form-group row"
+ *ngIf="action === 'edit'">
+ <label i18n
+ for="zones"
+ class="cd-col-form-label">Zones</label>
+ <div class="cd-col-form-input">
+ <cd-select-badges id="zones"
+ [data]="zonegroupZoneNames"
+ [options]="labelsOption"
+ [customBadges]="true">
+ </cd-select-badges><br>
+ <span class="invalid-feedback"
+ *ngIf="isRemoveMasterZone"
+ i18n>Cannot remove master zone.</span>
+ </div>
+ </div>
+ <div *ngIf="action === 'edit'">
+ <legend>Placement targets</legend>
+ <ng-container formArrayName="placementTargets">
+ <div *ngFor="let item of placementTargets.controls; let index = index; trackBy: trackByFn">
+ <div class="card"
+ [formGroup]="item">
+ <div class="card-header">
+ {{ (index + 1) | ordinal }}
+ <span class="float-end clickable"
+ name="remove_placement_target"
+ (click)="removePlacementTarget(index)"
+ ngbTooltip="Remove">×</span>
+ </div>
+
+ <div class="card-body">
+ <!-- Placement Id -->
+ <div class="form-group row">
+ <label i18n
+ class="cd-col-form-label required"
+ for="placement_id">Placement Id</label>
+ <div class="cd-col-form-input">
+ <input type="text"
+ class="form-control"
+ name="placement_id"
+ id="placement_id"
+ formControlName="placement_id"
+ placeholder="eg. default-placement">
+ <span class="invalid-feedback">
+ <span *ngIf="showError(index, 'placement_id', formDir, 'required')"
+ i18n>This field is required.</span>
+ </span>
+ </div>
+ </div>
+
+ <!-- Tags-->
+ <div class="form-group row">
+ <label i18n
+ class="cd-col-form-label"
+ for="tags">Tags</label>
+ <div class="cd-col-form-input">
+ <input type="text"
+ class="form-control"
+ name="tags"
+ id="tags"
+ formControlName="tags"
+ placeholder="comma separated tags, eg. default-placement, ssd">
+ </div>
+ </div>
+
+ <!-- Storage Class -->
+ <div class="form-group row">
+ <label i18n
+ class="cd-col-form-label"
+ for="storage_class">Storage Class</label>
+ <div class="cd-col-form-input">
+ <input type="text"
+ class="form-control"
+ name="storage_class"
+ id="storage_class"
+ formControlName="storage_class"
+ placeholder="eg. Standard-tier">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </ng-container>
+ <button type="button"
+ id="add-plc"
+ class="btn btn-light float-end my-3"
+ (click)="addPlacementTarget()">
+ <i [ngClass]="[icons.add]"></i>
+ <ng-container i18n>Add placement target</ng-container>
+ </button>
+ </div>
</div>
<div class="modal-footer">
<cd-form-button-panel (submitActionEvent)="submit()"
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import _ from 'lodash';
import { ToastrModule } from 'ngx-toastr';
+import { of as observableOf } from 'rxjs';
+import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+import { NotificationService } from '~/app/shared/services/notification.service';
import { SharedModule } from '~/app/shared/shared.module';
import { RgwMultisiteZonegroupFormComponent } from './rgw-multisite-zonegroup-form.component';
describe('RgwMultisiteZonegroupFormComponent', () => {
let component: RgwMultisiteZonegroupFormComponent;
let fixture: ComponentFixture<RgwMultisiteZonegroupFormComponent>;
+ let rgwZonegroupService: RgwZonegroupService;
beforeEach(async () => {
await TestBed.configureTestingModule({
it('should create', () => {
expect(component).toBeTruthy();
});
+
+ describe('submit form', () => {
+ let notificationService: NotificationService;
+
+ beforeEach(() => {
+ spyOn(TestBed.inject(Router), 'navigate').and.stub();
+ notificationService = TestBed.inject(NotificationService);
+ spyOn(notificationService, 'show');
+ rgwZonegroupService = TestBed.inject(RgwZonegroupService);
+ });
+
+ it('should validate name', () => {
+ component.action = 'create';
+ component.createForm();
+ const control = component.multisiteZonegroupForm.get('zonegroupName');
+ expect(_.isFunction(control.validator)).toBeTruthy();
+ });
+
+ it('should not validate name', () => {
+ component.action = 'edit';
+ component.createForm();
+ const control = component.multisiteZonegroupForm.get('zonegroupName');
+ expect(control.asyncValidator).toBeNull();
+ });
+
+ it('tests create success notification', () => {
+ spyOn(rgwZonegroupService, 'create').and.returnValue(observableOf([]));
+ component.action = 'create';
+ component.multisiteZonegroupForm.markAsDirty();
+ component.multisiteZonegroupForm._get('zonegroupName').setValue('zg-1');
+ component.multisiteZonegroupForm
+ ._get('zonegroup_endpoints')
+ .setValue('http://192.1.1.1:8004');
+ component.submit();
+ expect(notificationService.show).toHaveBeenCalledWith(
+ NotificationType.success,
+ "Zonegroup: 'zg-1' created successfully"
+ );
+ });
+
+ it('tests update success notification', () => {
+ spyOn(rgwZonegroupService, 'update').and.returnValue(observableOf([]));
+ component.action = 'edit';
+ component.info = {
+ data: { name: 'zg-1', zones: ['z1'] }
+ };
+ component.multisiteZonegroupForm._get('zonegroupName').setValue('zg-1');
+ component.multisiteZonegroupForm
+ ._get('zonegroup_endpoints')
+ .setValue('http://192.1.1.1:8004,http://192.12.12.12:8004');
+ component.multisiteZonegroupForm.markAsDirty();
+ component.submit();
+ expect(notificationService.show).toHaveBeenCalledWith(
+ NotificationType.success,
+ "Zonegroup: 'zg-1' updated successfully"
+ );
+ });
+ });
});
import { Component, OnInit } from '@angular/core';
-import { FormControl, Validators } from '@angular/forms';
+import { FormArray, FormBuilder, FormControl, NgForm, Validators } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import _ from 'lodash';
import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
import { CdValidators } from '~/app/shared/forms/cd-validators';
import { NotificationService } from '~/app/shared/services/notification.service';
-import { RgwRealm, RgwZonegroup } from '../models/rgw-multisite';
+import { RgwRealm, RgwZone, RgwZonegroup } from '../models/rgw-multisite';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { SelectOption } from '~/app/shared/components/select/select-option.model';
@Component({
selector: 'cd-rgw-multisite-zonegroup-form',
readonly ipv4Rgx = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i;
readonly ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i;
action: string;
+ icons = Icons;
multisiteZonegroupForm: CdFormGroup;
editing = false;
resource: string;
realm: RgwRealm;
zonegroup: RgwZonegroup;
+ info: any;
defaultsInfo: string[] = [];
multisiteInfo: object[] = [];
realmList: RgwRealm[] = [];
zonegroupList: RgwZonegroup[] = [];
zonegroupNames: string[];
isMaster = false;
+ placementTargets: FormArray;
+ newZonegroupName: string;
+ zonegroupZoneNames: string[];
+ labelsOption: Array<SelectOption> = [];
+ zoneList: RgwZone[] = [];
+ allZoneNames: string[];
+ zgZoneNames: string[];
+ zgZoneIds: string[];
+ removedZones: string[];
+ isRemoveMasterZone = false;
+ addedZones: string[];
+ disableDefault = false;
+ disableMaster = false;
constructor(
public activeModal: NgbActiveModal,
public actionLabels: ActionLabelsI18n,
public rgwZonegroupService: RgwZonegroupService,
- public notificationService: NotificationService
+ public notificationService: NotificationService,
+ private formBuilder: FormBuilder
) {
this.action = this.editing
? this.actionLabels.EDIT + this.resource
validators: [
Validators.required,
CdValidators.custom('uniqueName', (zonegroupName: string) => {
- return this.zonegroupNames && this.zonegroupNames.indexOf(zonegroupName) !== -1;
+ return (
+ this.action === 'create' &&
+ this.zonegroupNames &&
+ this.zonegroupNames.indexOf(zonegroupName) !== -1
+ );
})
]
}),
}
}),
Validators.required
- ])
+ ]),
+ placementTargets: this.formBuilder.array([])
});
}
ngOnInit(): void {
+ _.forEach(this.multisiteZonegroupForm.get('placementTargets'), (placementTarget) => {
+ const fg = this.addPlacementTarget();
+ fg.patchValue(placementTarget);
+ });
+ this.placementTargets = this.multisiteZonegroupForm.get('placementTargets') as FormArray;
this.realmList =
this.multisiteInfo[0] !== undefined && this.multisiteInfo[0].hasOwnProperty('realms')
? this.multisiteInfo[0]['realms']
? this.multisiteInfo[1]['zonegroups']
: [];
this.zonegroupList.forEach((zgp: any) => {
- if (
- zgp.is_master === true &&
- !_.isEmpty(zgp.realm_id) &&
- zgp.realm_id === this.defaultsInfo['defaultRealmName']
- ) {
+ if (zgp.is_master === true && !_.isEmpty(zgp.realm_id)) {
this.isMaster = true;
+ this.disableMaster = true;
}
});
if (!this.isMaster) {
this.multisiteZonegroupForm.get('master_zonegroup').setValue(true);
this.multisiteZonegroupForm.get('master_zonegroup').disable();
}
+ this.zoneList =
+ this.multisiteInfo[2] !== undefined && this.multisiteInfo[2].hasOwnProperty('zones')
+ ? this.multisiteInfo[2]['zones']
+ : [];
this.zonegroupNames = this.zonegroupList.map((zonegroup) => {
return zonegroup['name'];
});
+ this.allZoneNames = this.zoneList.map((zone: RgwZone) => {
+ return zone['name'];
+ });
if (this.action === 'create' && this.defaultsInfo['defaultRealmName'] !== null) {
this.multisiteZonegroupForm
.get('selectedRealm')
.setValue(this.defaultsInfo['defaultRealmName']);
+ if (this.disableMaster) {
+ this.multisiteZonegroupForm.get('master_zonegroup').disable();
+ }
+ }
+ if (this.action === 'edit') {
+ this.multisiteZonegroupForm.get('zonegroupName').setValue(this.info.data.name);
+ this.multisiteZonegroupForm.get('selectedRealm').setValue(this.info.data.parent);
+ this.multisiteZonegroupForm.get('default_zonegroup').setValue(this.info.data.is_default);
+ this.multisiteZonegroupForm.get('master_zonegroup').setValue(this.info.data.is_master);
+ this.multisiteZonegroupForm.get('zonegroup_endpoints').setValue(this.info.data.endpoints);
+
+ if (this.info.data.is_default) {
+ this.multisiteZonegroupForm.get('default_zonegroup').disable();
+ }
+ if (
+ !this.info.data.is_default &&
+ this.multisiteZonegroupForm.getValue('selectedRealm') !==
+ this.defaultsInfo['defaultRealmName']
+ ) {
+ this.multisiteZonegroupForm.get('default_zonegroup').disable();
+ this.disableDefault = true;
+ }
+ if (this.info.data.is_master || this.disableMaster) {
+ this.multisiteZonegroupForm.get('master_zonegroup').disable();
+ }
+
+ this.zonegroupZoneNames = this.info.data.zones.map((zone: { [x: string]: any }) => {
+ return zone['name'];
+ });
+ this.zgZoneNames = this.info.data.zones.map((zone: { [x: string]: any }) => {
+ return zone['name'];
+ });
+ this.zgZoneIds = this.info.data.zones.map((zone: { [x: string]: any }) => {
+ return zone['id'];
+ });
+ const uniqueZones = new Set(this.allZoneNames);
+ this.labelsOption = Array.from(uniqueZones).map((zone) => {
+ return { enabled: true, name: zone, selected: false, description: null };
+ });
+
+ this.info.data.placement_targets.forEach((target: object) => {
+ const fg = this.addPlacementTarget();
+ let data = {
+ placement_id: target['name'],
+ tags: target['tags'].join(','),
+ storage_class:
+ typeof target['storage_classes'] === 'string'
+ ? target['storage_classes']
+ : target['storage_classes'].join(',')
+ };
+ fg.patchValue(data);
+ });
}
}
submit() {
- const values = this.multisiteZonegroupForm.value;
- this.realm = new RgwRealm();
- this.realm.name = values['selectedRealm'];
- this.zonegroup = new RgwZonegroup();
- this.zonegroup.name = values['zonegroupName'];
- this.zonegroup.endpoints = this.checkUrlArray(values['zonegroup_endpoints']);
- this.rgwZonegroupService
- .create(this.realm, this.zonegroup, values['default_zonegroup'], values['master_zonegroup'])
- .subscribe(
- () => {
- this.notificationService.show(
- NotificationType.success,
- $localize`Zonegroup: '${values['zonegroupName']}' created successfully`
- );
- this.activeModal.close();
- },
- () => {
- this.multisiteZonegroupForm.setErrors({ cdSubmitButton: true });
- }
+ const values = this.multisiteZonegroupForm.getRawValue();
+ if (this.action === 'create') {
+ this.realm = new RgwRealm();
+ this.realm.name = values['selectedRealm'];
+ this.zonegroup = new RgwZonegroup();
+ this.zonegroup.name = values['zonegroupName'];
+ this.zonegroup.endpoints = this.checkUrlArray(values['zonegroup_endpoints']);
+ this.rgwZonegroupService
+ .create(this.realm, this.zonegroup, values['default_zonegroup'], values['master_zonegroup'])
+ .subscribe(
+ () => {
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`Zonegroup: '${values['zonegroupName']}' created successfully`
+ );
+ this.activeModal.close();
+ },
+ () => {
+ this.multisiteZonegroupForm.setErrors({ cdSubmitButton: true });
+ }
+ );
+ } else if (this.action === 'edit') {
+ this.removedZones = _.difference(this.zgZoneNames, this.zonegroupZoneNames);
+ const masterZoneName = this.info.data.zones.filter(
+ (zone: any) => zone.id === this.info.data.master_zone
);
+ this.isRemoveMasterZone = this.removedZones.includes(masterZoneName[0].name);
+ if (this.isRemoveMasterZone) {
+ this.multisiteZonegroupForm.setErrors({ cdSubmitButton: true });
+ return;
+ }
+ this.addedZones = _.difference(this.zonegroupZoneNames, this.zgZoneNames);
+ this.realm = new RgwRealm();
+ this.realm.name = values['selectedRealm'];
+ this.zonegroup = new RgwZonegroup();
+ this.zonegroup.name = this.info.data.name;
+ this.newZonegroupName = values['zonegroupName'];
+ this.zonegroup.endpoints =
+ values['zonegroup_endpoints'] === this.info.data.endpoints
+ ? values['zonegroup_endpoints']
+ : this.checkUrlArray(values['zonegroup_endpoints']);
+ this.zonegroup.placement_targets = values['placementTargets'];
+ this.rgwZonegroupService
+ .update(
+ this.realm,
+ this.zonegroup,
+ this.newZonegroupName,
+ values['default_zonegroup'],
+ values['master_zonegroup'],
+ this.removedZones,
+ this.addedZones
+ )
+ .subscribe(
+ () => {
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`Zonegroup: '${values['zonegroupName']}' updated successfully`
+ );
+ this.activeModal.close();
+ },
+ () => {
+ this.multisiteZonegroupForm.setErrors({ cdSubmitButton: true });
+ }
+ );
+ }
}
checkUrlArray(endpoints: string) {
}
return endpointsArray;
}
+
+ addPlacementTarget() {
+ this.placementTargets = this.multisiteZonegroupForm.get('placementTargets') as FormArray;
+ const fg = new CdFormGroup({
+ placement_id: new FormControl('', {
+ validators: [Validators.required]
+ }),
+ tags: new FormControl(''),
+ storage_class: new FormControl([])
+ });
+ this.placementTargets.push(fg);
+ return fg;
+ }
+
+ trackByFn(index: number) {
+ return index;
+ }
+
+ removePlacementTarget(index: number) {
+ this.placementTargets = this.multisiteZonegroupForm.get('placementTargets') as FormArray;
+ this.placementTargets.removeAt(index);
+ }
+
+ showError(index: number, control: string, formDir: NgForm, x: string) {
+ return (<any>this.multisiteZonegroupForm.controls.placementTargets).controls[index].showError(
+ control,
+ formDir,
+ x
+ );
+ }
}
});
}
+ update(
+ realm: RgwRealm,
+ zonegroup: RgwZonegroup,
+ newZonegroupName: string,
+ defaultZonegroup: boolean,
+ master: boolean,
+ removedZones: string[],
+ addedZones: string[]
+ ) {
+ return this.rgwDaemonService.request((requestBody: any) => {
+ requestBody = {
+ zonegroup_name: zonegroup.name,
+ realm_name: realm.name,
+ new_zonegroup_name: newZonegroupName,
+ default: defaultZonegroup,
+ master: master,
+ zonegroup_endpoints: zonegroup.endpoints,
+ placement_targets: zonegroup.placement_targets,
+ remove_zones: removedZones,
+ add_zones: addedZones
+ };
+ return this.http.put(`${this.url}/${zonegroup.name}`, requestBody);
+ });
+ }
+
list(): Observable<object> {
return this.rgwDaemonService.request(() => {
return this.http.get<object>(`${this.url}`);
nodes['type'] = 'zonegroup';
nodes['endpoints'] = zonegroup.endpoints;
nodes['master_zone'] = zonegroup.master_zone;
+ nodes['zones'] = zonegroup.zones;
+ nodes['placement_targets'] = zonegroup.placement_targets;
return nodes;
}
}
- jwt: []
tags:
- RgwZonegroup
+ put:
+ parameters:
+ - in: path
+ name: zonegroup_name
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ add_zones:
+ default: []
+ type: string
+ daemon_name:
+ type: string
+ default:
+ default: ''
+ type: string
+ master:
+ default: ''
+ type: string
+ new_zonegroup_name:
+ type: string
+ placement_targets:
+ default: []
+ type: string
+ realm_name:
+ type: string
+ remove_zones:
+ default: []
+ type: string
+ zonegroup_endpoints:
+ default: []
+ type: string
+ required:
+ - realm_name
+ - new_zonegroup_name
+ 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:
+ - RgwZonegroup
/api/role:
get:
parameters: []
exit_code, _, err = mgr.send_rgwadmin_command(rgw_update_period_cmd)
if exit_code > 0:
raise DashboardException(e=err, msg='Unable to update period',
- http_status_code=400, component='rgw')
+ http_status_code=500, component='rgw')
except SubprocessError as error:
raise DashboardException(error, http_status_code=500, component='rgw')
raise DashboardException(error, http_status_code=500, component='rgw')
return out
+ def modify_zonegroup(self, realm_name: str, zonegroup_name: str, default: str, master: str,
+ endpoints: List[str]):
+ if realm_name:
+ rgw_zonegroup_modify_cmd = ['zonegroup', 'modify',
+ '--rgw-realm', realm_name,
+ '--rgw-zonegroup', zonegroup_name]
+ if endpoints:
+ if len(endpoints) > 1:
+ endpoint = ','.join(str(e) for e in endpoints)
+ else:
+ endpoint = endpoints[0]
+ rgw_zonegroup_modify_cmd.append('--endpoints')
+ rgw_zonegroup_modify_cmd.append(endpoint)
+ if master and str_to_bool(master):
+ rgw_zonegroup_modify_cmd.append('--master')
+ if default and str_to_bool(default):
+ rgw_zonegroup_modify_cmd.append('--default')
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_modify_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to modify zonegroup {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.update_period()
+
+ def add_or_remove_zone(self, zonegroup_name: str, zone_name: str, action: str):
+ if action == 'add':
+ rgw_zonegroup_add_zone_cmd = ['zonegroup', 'add', '--rgw-zonegroup',
+ zonegroup_name, '--rgw-zone', zone_name]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_add_zone_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to add zone {} to zonegroup {}'.format(zone_name, zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.update_period()
+ if action == 'remove':
+ rgw_zonegroup_rm_zone_cmd = ['zonegroup', 'remove',
+ '--rgw-zonegroup', zonegroup_name, '--rgw-zone', zone_name]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_rm_zone_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to remove zone {} from zonegroup {}'.format(zone_name, zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.update_period()
+
+ def get_placement_targets_by_zonegroup(self, zonegroup_name: str):
+ rgw_get_placement_cmd = ['zonegroup', 'placement',
+ 'list', '--rgw-zonegroup', zonegroup_name]
+ try:
+ exit_code, out, err = mgr.send_rgwadmin_command(rgw_get_placement_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to get placement targets',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ return out
+
+ def add_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]):
+ rgw_add_placement_cmd = ['zonegroup', 'placement', 'add']
+ for placement_target in placement_targets:
+ cmd_add_placement_options = ['--rgw-zonegroup', zonegroup_name,
+ '--placement-id', placement_target['placement_id']]
+ if placement_target['tags']:
+ cmd_add_placement_options += ['--tags', placement_target['tags']]
+ rgw_add_placement_cmd += cmd_add_placement_options
+ storage_classes = placement_target['storage_class'].split(",") if placement_target['storage_class'] else [] # noqa E501 #pylint: disable=line-too-long
+ if storage_classes:
+ for sc in storage_classes:
+ cmd_add_placement_options = ['--storage-class', sc]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(
+ rgw_add_placement_cmd + cmd_add_placement_options)
+ if exit_code > 0:
+ raise DashboardException(e=err,
+ msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.update_period()
+ return
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_add_placement_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err,
+ msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.update_period()
+
+ def modify_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]):
+ rgw_add_placement_cmd = ['zonegroup', 'placement', 'modify']
+ for placement_target in placement_targets:
+ cmd_add_placement_options = ['--rgw-zonegroup', zonegroup_name,
+ '--placement-id', placement_target['placement_id']]
+ if placement_target['tags']:
+ cmd_add_placement_options += ['--tags', placement_target['tags']]
+ rgw_add_placement_cmd += cmd_add_placement_options
+ storage_classes = placement_target['storage_class'].split(",") if placement_target['storage_class'] else [] # noqa E501 #pylint: disable=line-too-long
+ if storage_classes:
+ for sc in storage_classes:
+ cmd_add_placement_options = []
+ cmd_add_placement_options = ['--storage-class', sc]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(
+ rgw_add_placement_cmd + cmd_add_placement_options)
+ if exit_code > 0:
+ raise DashboardException(e=err,
+ msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.update_period()
+ else:
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_add_placement_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err,
+ msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.update_period()
+
+ # pylint: disable=W0102
+ def edit_zonegroup(self, realm_name: str, zonegroup_name: str, new_zonegroup_name: str,
+ default: str = '', master: str = '', endpoints: List[str] = [],
+ add_zones: List[str] = [], remove_zones: List[str] = [],
+ placement_targets: List[Dict[str, str]] = []):
+ rgw_zonegroup_edit_cmd = []
+ if new_zonegroup_name != zonegroup_name:
+ rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', zonegroup_name,
+ '--zonegroup-new-name', new_zonegroup_name]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_edit_cmd, False)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to rename zonegroup to {}'.format(new_zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.update_period()
+ self.modify_zonegroup(realm_name, new_zonegroup_name, default, master, endpoints)
+ if add_zones:
+ for zone_name in add_zones:
+ self.add_or_remove_zone(new_zonegroup_name, zone_name, 'add')
+ if remove_zones:
+ for zone_name in remove_zones:
+ self.add_or_remove_zone(new_zonegroup_name, zone_name, 'remove')
+ existing_placement_targets = self.get_placement_targets_by_zonegroup(new_zonegroup_name)
+ existing_placement_targets_ids = [pt['key'] for pt in existing_placement_targets]
+ if placement_targets:
+ for pt in placement_targets:
+ if pt['placement_id'] in existing_placement_targets_ids:
+ self.modify_placement_targets(new_zonegroup_name, placement_targets)
+ else:
+ self.add_placement_targets(new_zonegroup_name, placement_targets)
+
def list_zonegroups(self):
rgw_zonegroup_list = {}
rgw_zonegroup_list_cmd = ['zonegroup', 'list']