@EndpointDoc("Create or update the sync flow")
@CreatePermission
def create_sync_flow(self, flow_id: str, flow_type: str, group_id: str,
- source_zone: Optional[List[str]] = None,
- destination_zone: Optional[List[str]] = None,
- zones: Optional[List[str]] = None,
+ source_zone: Optional[str] = None,
+ destination_zone: Optional[str] = None,
+ zones: Optional[Dict[str, List]] = None,
bucket_name=''):
multisite_instance = RgwMultisite()
return multisite_instance.create_sync_flow(group_id, flow_id, flow_type, zones,
@EndpointDoc("Create or update the sync pipe")
@CreatePermission
def create_sync_pipe(self, group_id: str, pipe_id: str,
+ source_zones: Dict[str, Any],
+ destination_zones: Dict[str, Any],
source_bucket: str = '',
- source_zones: Optional[List[str]] = None,
- destination_zones: Optional[List[str]] = None,
- destination_bucket: str = '',
- bucket_name: str = ''):
+ destination_bucket: str = '', bucket_name: str = ''):
multisite_instance = RgwMultisite()
return multisite_instance.create_sync_pipe(group_id, pipe_id, source_zones,
destination_zones, source_bucket,
directional = 'directional',
symmetrical = 'symmetrical'
}
+
+export interface Zone {
+ added: string[];
+ removed: string[];
+}
i18n>Source Zone
</label>
<div class="cd-col-form-input">
- <ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'source_zone', zone: sourceZones }"></ng-container>
+ <select id="sourceZone"
+ name="sourceZone"
+ class="form-select"
+ formControlName="source_zone"
+ [autofocus]="editing">
+ <option i18n
+ *ngIf="zones.data.available.length == 0"
+ [ngValue]="null">Loading...</option>
+ <option i18n
+ *ngIf="zones.data.available.length > 0"
+ [ngValue]="null">-- Select source zone --</option>
+ <option *ngFor="let sourceZone of zones.data.available"
+ [value]="sourceZone.name">{{ sourceZone.name }}</option>
+ </select>
+ <span class="invalid-feedback"
+ *ngIf="currentFormGroupContext.showError('source_zone', frm, 'required')"
+ i18n>This field is required.</span>
</div>
</div>
<div class="form-group row">
for="destination_zone"
i18n>Destination Zone</label>
<div class="cd-col-form-input">
- <ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'destination_zone', zone: destinationZones }"></ng-container>
+ <input id="destination_zone"
+ name="destination_zone"
+ class="form-control"
+ type="text"
+ i18n-placeholder
+ placeholder="Destination Zone..."
+ formControlName="destination_zone"/>
+ <span class="invalid-feedback"
+ *ngIf="currentFormGroupContext.showError('destination_zone', frm, 'required')"
+ i18n>This field is required.</span>
</div>
</div>
</ng-template>
import { UntypedFormControl, Validators } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
-import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { ActionLabelsI18n, SucceededActionLabelsI18n } from '~/app/shared/constants/app.constants';
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
import { NotificationService } from '~/app/shared/services/notification.service';
import { catchError, switchMap } from 'rxjs/operators';
import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
import { RgwDaemon } from '../models/rgw-daemon';
-import { FlowType, RgwZonegroup } from '../models/rgw-multisite';
+import { FlowType, RgwZonegroup, Zone } from '../models/rgw-multisite';
import { of } from 'rxjs';
import { SelectOption } from '~/app/shared/components/select/select-option.model';
import _ from 'lodash';
flowType = FlowType;
icons = Icons;
zones = new ZoneData(false, 'Filter Zones');
- sourceZones = new ZoneData(false, 'Filter Zones');
- destinationZones = new ZoneData(true, 'Filter or Add Zones');
+ sourceZone: string;
+ destinationZone: string;
constructor(
public activeModal: NgbActiveModal,
public notificationService: NotificationService,
private rgwDaemonService: RgwDaemonService,
private rgwZonegroupService: RgwZonegroupService,
- private rgwMultisiteService: RgwMultisiteService
+ private rgwMultisiteService: RgwMultisiteService,
+ private succeededLabels: SucceededActionLabelsI18n
) {}
ngOnInit(): void {
zones.push(new SelectOption(false, zone.name, ''));
});
this.zones.data.available = [...zones];
- this.sourceZones.data.available = [...zones];
if (this.editing) {
if (this.groupType === FlowType.symmetrical) {
- this.zones.data.selected = this.flowSelectedRow.zones;
- } else {
- this.destinationZones.data.selected = [this.flowSelectedRow.dest_zone];
- this.sourceZones.data.selected = [this.flowSelectedRow.source_zone];
+ this.zones.data.selected = [...this.flowSelectedRow.zones];
}
this.zoneSelection();
}
this.currentFormGroupContext.patchValue({
zones: this.zones.data.selected
});
- } else {
- this.currentFormGroupContext.patchValue({
- source_zone: this.sourceZones.data.selected,
- destination_zone: this.destinationZones.data.selected
- });
}
}
+ getZoneData(zoneDataToFilter: string[], zoneDataForCondition: string[]) {
+ return zoneDataToFilter.filter((zone: string) => !zoneDataForCondition.includes(zone));
+ }
+
+ assignZoneValue(zone: string[], selectedZone: string[]) {
+ return zone.length > 0 ? zone : selectedZone;
+ }
+
submit() {
+ const zones: Zone = { added: [], removed: [] };
+
if (this.currentFormGroupContext.invalid) {
return;
}
this.currentFormGroupContext.setErrors({ cdSubmitButton: true });
return;
}
+
+ if (this.groupType == FlowType.symmetrical) {
+ if (this.editing) {
+ zones.removed = this.getZoneData(this.flowSelectedRow.zones, this.zones.data.selected);
+ zones.added = this.getZoneData(this.zones.data.selected, this.flowSelectedRow.zones);
+ }
+ zones.added = this.assignZoneValue(zones.added, this.zones.data.selected);
+ }
this.rgwMultisiteService
- .createEditSyncFlow(this.currentFormGroupContext.getRawValue())
+ .createEditSyncFlow({ ...this.currentFormGroupContext.getRawValue(), zones: zones })
.subscribe(
() => {
- const action = this.editing ? 'Modified' : 'Created';
+ const action = this.editing ? this.succeededLabels.EDITED : this.succeededLabels.CREATED;
this.notificationService.show(
NotificationType.success,
$localize`${action} Sync Flow '${this.currentFormGroupContext.getValue('flow_id')}'`
);
- this.activeModal.close('success');
+ this.activeModal.close(NotificationType.success);
},
() => {
// Reset the 'Submit' button.
import { Component, OnInit } from '@angular/core';
import { UntypedFormControl, Validators } from '@angular/forms';
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
-import { RgwZonegroup } from '../models/rgw-multisite';
+import { RgwZonegroup, Zone } from '../models/rgw-multisite';
import { SelectOption } from '~/app/shared/components/select/select-option.model';
import { catchError, switchMap } from 'rxjs/operators';
import { of } from 'rxjs';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { NotificationService } from '~/app/shared/services/notification.service';
import { ZoneData } from '../models/rgw-multisite-zone-selector';
+import { SucceededActionLabelsI18n } from '~/app/shared/constants/app.constants';
@Component({
selector: 'cd-rgw-multisite-sync-pipe-modal',
private rgwDaemonService: RgwDaemonService,
private rgwZonegroupService: RgwZonegroupService,
private rgwMultisiteService: RgwMultisiteService,
- private notificationService: NotificationService
+ private notificationService: NotificationService,
+ private succeededLabels: SucceededActionLabelsI18n
) {}
ngOnInit(): void {
this.sourceZones.data.available = [...zones];
if (this.editing) {
this.pipeForm.get('pipe_id').disable();
- this.sourceZones.data.selected = this.pipeSelectedRow.source.zones;
- this.destZones.data.selected = this.pipeSelectedRow.dest.zones;
+ this.sourceZones.data.selected = [...this.pipeSelectedRow.source.zones];
+ this.destZones.data.selected = [...this.pipeSelectedRow.dest.zones];
+ const availableDestZone: SelectOption[] = [];
+ this.pipeSelectedRow.dest.zones.forEach((zone: string) => {
+ availableDestZone.push(new SelectOption(true, zone, ''));
+ });
+ this.destZones.data.available = availableDestZone;
this.pipeForm.patchValue({
pipe_id: this.pipeSelectedRow.id,
source_zones: this.pipeSelectedRow.source.zones,
}
}
+ getZoneData(zoneDataToFilter: string[], zoneDataForCondition: string[]) {
+ return zoneDataToFilter.filter((zone: string) => !zoneDataForCondition.includes(zone));
+ }
+
+ assignZoneValue(zone: string[], selectedZone: string[]) {
+ return zone.length > 0 ? zone : selectedZone;
+ }
+
submit() {
+ const sourceZones: Zone = { added: [], removed: [] };
+ const destZones: Zone = { added: [], removed: [] };
+
if (this.pipeForm.invalid) {
return;
}
this.pipeForm.setErrors({ cdSubmitButton: true });
return;
}
- this.rgwMultisiteService.createEditSyncPipe(this.pipeForm.getRawValue()).subscribe(
- () => {
- const action = this.editing ? 'Modified' : 'Created';
- this.notificationService.show(
- NotificationType.success,
- $localize`${action} Sync Pipe '${this.pipeForm.getValue('pipe_id')}'`
- );
- this.activeModal.close('success');
- },
- () => {
- // Reset the 'Submit' button.
- this.pipeForm.setErrors({ cdSubmitButton: true });
- this.activeModal.dismiss();
- }
- );
+
+ if (this.editing) {
+ destZones.removed = this.getZoneData(
+ this.pipeSelectedRow.dest.zones,
+ this.destZones.data.selected
+ );
+ destZones.added = this.getZoneData(
+ this.destZones.data.selected,
+ this.pipeSelectedRow.dest.zones
+ );
+ sourceZones.removed = this.getZoneData(
+ this.pipeSelectedRow.source.zones,
+ this.sourceZones.data.selected
+ );
+ sourceZones.added = this.getZoneData(
+ this.sourceZones.data.selected,
+ this.pipeSelectedRow.source.zones
+ );
+ }
+ sourceZones.added = this.assignZoneValue(sourceZones.added, this.sourceZones.data.selected);
+ destZones.added = this.assignZoneValue(destZones.added, this.destZones.data.selected);
+
+ this.rgwMultisiteService
+ .createEditSyncPipe({
+ ...this.pipeForm.getRawValue(),
+ source_zones: sourceZones,
+ destination_zones: destZones
+ })
+ .subscribe(
+ () => {
+ const action = this.editing ? this.succeededLabels.EDITED : this.succeededLabels.CREATED;
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`${action} Sync Pipe '${this.pipeForm.getValue('pipe_id')}'`
+ );
+ this.activeModal.close(NotificationType.success);
+ },
+ () => {
+ // Reset the 'Submit' button.
+ this.pipeForm.setErrors({ cdSubmitButton: true });
+ this.activeModal.dismiss();
+ }
+ );
}
}
</cd-table-actions>
</div>
</cd-table>
- <cd-alert-panel
- *ngIf="dirFlowSelection.hasSelection"
- type="info"
- title="Directional Flow 'edit' & 'delete' actions disabled"
- i18n-title
- i18n>
- Due to some internal dependencies these actions are disabled, it will get enabled once the issue gets resolved.
- </cd-alert-panel>
</ng-template>
</ng-container>
<ng-container ngbNavItem="pipe">
<ng-template #deleteTpl>
<cd-alert-panel type="danger"
i18n>
- Are you sure you want to delete these Flow?
+ Deleting {{ resourceType | upperFirst }} may disrupt data synchronization
</cd-alert-panel>
</ng-template>
import { RgwMultisiteSyncFlowModalComponent } from '../rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component';
import { FlowType } from '../models/rgw-multisite';
import { RgwMultisiteSyncPipeModalComponent } from '../rgw-multisite-sync-pipe-modal/rgw-multisite-sync-pipe-modal.component';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+
+enum MultisiteResourceType {
+ flow = 'flow',
+ pipe = 'pipe'
+}
@Component({
selector: 'cd-rgw-multisite-sync-policy-details',
@ViewChild('deleteTpl', { static: true })
deleteTpl: TemplateRef<any>;
+ resourceType: MultisiteResourceType = MultisiteResourceType.flow;
flowType = FlowType;
modalRef: NgbModalRef;
symmetricalFlowData: any = [];
click: () => this.openModal(FlowType.directional),
canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
};
- const dirEditAction: CdTableAction = {
- permission: 'update',
- icon: Icons.edit,
- name: this.actionLabels.EDIT,
- click: () => this.openModal(FlowType.directional, true),
- disable: () => true // TODO: disabling 'edit' as we are not getting flow ID from backend which is needed for edit
- };
const dirDeleteAction: CdTableAction = {
permission: 'delete',
icon: Icons.destroy,
- disable: () => true, // TODO: disabling 'delete' as we are not getting flow ID from backend which is needed for deletion
+ // TODO: disabling 'delete' as we are not getting flow_id from backend which is needed for deletion
+ disable: () =>
+ 'Deleting the directional flow is disabled in the UI. Please use CLI to delete the directional flow',
name: this.actionLabels.DELETE,
click: () => this.deleteFlow(FlowType.directional),
- canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection
+ canBePrimary: (selection: CdTableSelection) => selection.hasSelection
};
- this.dirFlowTableActions = [dirAddAction, dirEditAction, dirDeleteAction];
+ this.dirFlowTableActions = [dirAddAction, dirDeleteAction];
const pipeAddAction: CdTableAction = {
permission: 'create',
icon: Icons.add,
try {
const res = await this.modalRef.result;
- if (res === 'success') {
+ if (res === NotificationType.success) {
this.loadData();
}
} catch (err) {}
}
deleteFlow(flowType: FlowType) {
+ this.resourceType = MultisiteResourceType.flow;
let selection = this.symFlowSelection;
if (flowType === FlowType.directional) {
selection = this.dirFlowSelection;
try {
const res = await this.modalRef.result;
- if (res === 'success') {
+ if (res === NotificationType.success) {
this.loadData();
}
} catch (err) {}
}
deletePipe() {
+ this.resourceType = MultisiteResourceType.pipe;
const pipeIds = this.pipeSelection.selected.map((pipe: any) => pipe.id);
this.modalService.show(CriticalConfirmationModalComponent, {
itemDescription: this.pipeSelection.hasSingleSelection ? $localize`Pipe` : $localize`Pipes`,
{ path: '', redirectTo: 'configuration', pathMatch: 'full' },
{
path: 'configuration',
- component: RgwMultisiteDetailsComponent
+ component: RgwMultisiteDetailsComponent,
+ data: { breadcrumbs: 'Configuration' }
},
{
path: 'sync-policy',
component: RgwMultisiteSyncPolicyComponent,
+ data: { breadcrumbs: 'Sync-policy' },
children: [
{
path: `${URLVerbs.CREATE}`,
required:
- group_id
- pipe_id
+ - source_zones
+ - destination_zones
type: object
responses:
'200':
raise DashboardException(error, http_status_code=500, component='rgw')
def create_sync_flow(self, group_id: str, flow_id: str, flow_type: str,
- zones: Optional[List[str]] = None, bucket_name: str = '',
- source_zone: Optional[List[str]] = None,
- destination_zone: Optional[List[str]] = None):
+ zones: Optional[Dict[str, List]] = None, bucket_name: str = '',
+ source_zone: Optional[str] = None,
+ destination_zone: Optional[str] = None):
rgw_sync_policy_cmd = ['sync', 'group', 'flow', 'create', '--group-id', group_id,
'--flow-id', flow_id, '--flow-type', SyncFlowTypes[flow_type].value]
+ if bucket_name:
+ rgw_sync_policy_cmd += ['--bucket', bucket_name]
+
if SyncFlowTypes[flow_type].value == 'directional':
+
if source_zone is not None:
- rgw_sync_policy_cmd += ['--source-zone', ','.join(source_zone)]
+ rgw_sync_policy_cmd += ['--source-zone', source_zone]
+
if destination_zone is not None:
- rgw_sync_policy_cmd += ['--dest-zone', ','.join(destination_zone)]
+ rgw_sync_policy_cmd += ['--dest-zone', destination_zone]
+
+ logger.info("Creating directional flow! %s", rgw_sync_policy_cmd)
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
+ if exit_code > 0:
+ raise DashboardException(f'Unable to create sync flow: {err}',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
else:
- if zones:
- rgw_sync_policy_cmd += ['--zones', ','.join(zones)]
+ if zones is not None and (zones['added'] or zones['removed']):
+ if len(zones['added']) > 0:
+ rgw_sync_policy_cmd += ['--zones', ','.join(zones['added'])]
- if bucket_name:
- rgw_sync_policy_cmd += ['--bucket', bucket_name]
+ logger.info("Creating symmetrical flow! %s", rgw_sync_policy_cmd)
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
+ if exit_code > 0:
+ raise DashboardException(f'Unable to create sync flow: {err}',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
- if exit_code > 0:
- raise DashboardException(f'Unable to create sync flow: {err}',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
+ if len(zones['removed']) > 0:
+ self.remove_sync_flow(group_id, flow_id, flow_type, source_zone,
+ destination_zone, zones['removed'], bucket_name)
def remove_sync_flow(self, group_id: str, flow_id: str, flow_type: str,
source_zone='', destination_zone='',
if bucket_name:
rgw_sync_policy_cmd += ['--bucket', bucket_name]
+ logger.info("Removing sync flow! %s", rgw_sync_policy_cmd)
try:
exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
if exit_code > 0:
raise DashboardException(error, http_status_code=500, component='rgw')
def create_sync_pipe(self, group_id: str, pipe_id: str,
- source_zones: Optional[List[str]] = None,
- destination_zones: Optional[List[str]] = None,
+ source_zones: Dict[str, Any],
+ destination_zones: Dict[str, Any],
source_bucket: str = '',
destination_bucket: str = '',
bucket_name: str = ''):
- rgw_sync_policy_cmd = ['sync', 'group', 'pipe', 'create',
- '--group-id', group_id, '--pipe-id', pipe_id]
- if bucket_name:
- rgw_sync_policy_cmd += ['--bucket', bucket_name]
+ if source_zones['added'] or destination_zones['added']:
+ rgw_sync_policy_cmd = ['sync', 'group', 'pipe', 'create',
+ '--group-id', group_id, '--pipe-id', pipe_id]
- if source_zones:
- rgw_sync_policy_cmd += ['--source-zones', ','.join(source_zones)]
+ if bucket_name:
+ rgw_sync_policy_cmd += ['--bucket', bucket_name]
- if destination_zones:
- rgw_sync_policy_cmd += ['--dest-zones', ','.join(destination_zones)]
+ if source_bucket:
+ rgw_sync_policy_cmd += ['--source-bucket', source_bucket]
- if source_bucket:
- rgw_sync_policy_cmd += ['--source-bucket', source_bucket]
+ if destination_bucket:
+ rgw_sync_policy_cmd += ['--dest-bucket', destination_bucket]
- if destination_bucket:
- rgw_sync_policy_cmd += ['--dest-bucket', destination_bucket]
+ if source_zones['added']:
+ rgw_sync_policy_cmd += ['--source-zones', ','.join(source_zones['added'])]
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
- if exit_code > 0:
- raise DashboardException(f'Unable to create sync pipe: {err}',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
+ if destination_zones['added']:
+ rgw_sync_policy_cmd += ['--dest-zones', ','.join(destination_zones['added'])]
+
+ logger.info("Creating sync pipe!")
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
+ if exit_code > 0:
+ raise DashboardException(f'Unable to create sync pipe: {err}',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ if source_zones['removed'] or destination_zones['removed']:
+ self.remove_sync_pipe(group_id, pipe_id, source_zones['removed'],
+ destination_zones['removed'], destination_bucket,
+ bucket_name)
def remove_sync_pipe(self, group_id: str, pipe_id: str,
source_zones: Optional[List[str]] = None,
if destination_bucket:
rgw_sync_policy_cmd += ['--dest-bucket', destination_bucket]
+ logger.info("Removing sync pipe! %s", rgw_sync_policy_cmd)
try:
exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
if exit_code > 0:
# create a sync flow with source and destination zones
self.create_sync_flow(_SYNC_GROUP_ID, _SYNC_FLOW_ID,
SyncFlowTypes.symmetrical.value,
- zones=zone_names)
+ zones={'added': zone_names, 'removed': []})
# create a sync pipe with source and destination zones
- self.create_sync_pipe(_SYNC_GROUP_ID, _SYNC_PIPE_ID, source_zones=['*'],
- destination_zones=['*'], source_bucket='*', destination_bucket='*')
+ self.create_sync_pipe(_SYNC_GROUP_ID, _SYNC_PIPE_ID,
+ source_zones={'added': '*', 'removed': []},
+ destination_zones={'added': '*', 'removed': []}, source_bucket='*',
+ destination_bucket='*')
# period update --commit
self.update_period()