@EndpointDoc("Create or update the sync pipe")
@CreatePermission
def create_sync_pipe(self, group_id: str, pipe_id: str,
+ source_bucket: str = '',
source_zones: Optional[List[str]] = None,
destination_zones: Optional[List[str]] = None,
- destination_buckets: Optional[List[str]] = None,
+ destination_bucket: str = '',
bucket_name: str = ''):
multisite_instance = RgwMultisite()
return multisite_instance.create_sync_pipe(group_id, pipe_id, source_zones,
- destination_zones, destination_buckets,
- bucket_name)
+ destination_zones, source_bucket,
+ destination_bucket, bucket_name)
@Endpoint(method='DELETE', path='/sync-pipe')
@EndpointDoc("Remove the sync pipe")
def remove_sync_pipe(self, group_id: str, pipe_id: str,
source_zones: Optional[List[str]] = None,
destination_zones: Optional[List[str]] = None,
- destination_buckets: Optional[List[str]] = None,
+ destination_bucket: str = '',
bucket_name: str = ''):
multisite_instance = RgwMultisite()
return multisite_instance.remove_sync_pipe(group_id, pipe_id, source_zones,
- destination_zones, destination_buckets,
+ destination_zones, destination_bucket,
bucket_name)
}
getNestedTableCell(
- tableSelector: string,
+ selector: string,
columnIndex: number,
exactContent: string,
partialMatch = false
) {
this.waitDataTableToLoad();
this.clearTableSearchInput();
- this.searchNestedTable(tableSelector, exactContent);
+ this.searchNestedTable(selector, exactContent);
if (partialMatch) {
return cy
- .get(`${tableSelector} datatable-body-row datatable-body-cell:nth-child(${columnIndex})`)
+ .get(`${selector} datatable-body-row datatable-body-cell:nth-child(${columnIndex})`)
.should('contain', exactContent);
}
return cy
- .get(`${tableSelector}`)
+ .get(`${selector}`)
.contains(
`datatable-body-row datatable-body-cell:nth-child(${columnIndex})`,
new RegExp(`^${exactContent}$`)
);
}
- searchNestedTable(tableSelector: string, text: string) {
+ searchNestedTable(selector: string, text: string) {
this.waitDataTableToLoad();
this.setPageSize('10');
- cy.get(`${tableSelector} [aria-label=search]`).first().clear({ force: true }).type(text);
+ cy.get(`${selector} [aria-label=search]`).first().clear({ force: true }).type(text);
}
}
);
});
});
+
+ describe('create, edit, delete pipe', () => {
+ beforeEach(() => {
+ multisite.getTab('Sync Policy').click();
+ multisite.getExpandCollapseElement().click();
+ });
+
+ it('should create pipe', () => {
+ multisite.createPipe('new-pipe', ['zone1-zg1-realm1'], ['zone3-zg2-realm1']);
+ });
+
+ it('should modify pipe zones', () => {
+ multisite.editPipe('new-pipe', 'zone2-zg1-realm1');
+ });
+
+ it('should delete pipe', () => {
+ multisite.deletePipe('new-pipe');
+ });
+ });
});
.find('.datatable-body-cell-label')
.should('contain', dest_zones[0]);
}
+
+ @PageHelper.restrictTo(pages.index.url)
+ createPipe(pipe_id: string, source_zones: string[], dest_zones: string[]) {
+ cy.get('cd-rgw-multisite-sync-policy-details').should('exist');
+ this.getTab('Pipe').should('exist');
+ this.getTab('Pipe').click();
+ cy.request({
+ method: 'GET',
+ url: '/api/rgw/daemon',
+ headers: { Accept: 'application/vnd.ceph.api.v1.0+json' }
+ });
+ cy.get('cd-rgw-multisite-sync-policy-details .table-actions button').first().click();
+ cy.get('cd-rgw-multisite-sync-pipe-modal').should('exist');
+
+ // Enter in pipe_id
+ cy.get('#pipe_id').type(pipe_id);
+ cy.wait(WAIT_TIMER);
+ // Select zone
+ cy.get('a[data-testid=select-menu-edit]').eq(0).click();
+ for (const zone of source_zones) {
+ cy.get('.popover-body div.select-menu-item-content').contains(zone).click();
+ }
+ cy.get('cd-rgw-multisite-sync-pipe-modal').click();
+ cy.get('a[data-testid=select-menu-edit]').eq(1).click();
+ for (const zone of dest_zones) {
+ cy.get('.popover-body input').type(`${zone}{enter}`);
+ }
+ cy.get('button.tc_submitButton').click();
+
+ cy.get('cd-rgw-multisite-sync-policy-details .datatable-body-cell-label').should(
+ 'contain',
+ pipe_id
+ );
+
+ cy.get('cd-rgw-multisite-sync-policy-details')
+ .first()
+ .find('[aria-label=search]')
+ .first()
+ .clear({ force: true })
+ .type(pipe_id);
+ }
+
+ @PageHelper.restrictTo(pages.index.url)
+ editPipe(pipe_id: string, zoneToAdd: string) {
+ cy.get('cd-rgw-multisite-sync-policy-details').should('exist');
+ this.getTab('Pipe').should('exist');
+ this.getTab('Pipe').click();
+ cy.request({
+ method: 'GET',
+ url: '/api/rgw/daemon',
+ headers: { Accept: 'application/vnd.ceph.api.v1.0+json' }
+ });
+
+ cy.get('cd-rgw-multisite-sync-policy-details').within(() => {
+ cy.get('.datatable-body-cell-label').should('contain', pipe_id);
+ cy.get('[aria-label=search]').first().clear({ force: true }).type(pipe_id);
+ cy.get('input.cd-datatable-checkbox').first().check();
+ cy.get('.table-actions button').first().click();
+ });
+ cy.get('cd-rgw-multisite-sync-pipe-modal').should('exist');
+
+ cy.wait(WAIT_TIMER);
+ // Enter in pipe_id
+ cy.get('#pipe_id').should('contain.value', pipe_id);
+ // Select zone
+ cy.get('a[data-testid=select-menu-edit]').eq(1).click();
+
+ cy.get('.popover-body input').type(`${zoneToAdd}{enter}`);
+
+ cy.get('button.tc_submitButton').click();
+
+ this.getNestedTableCell('cd-rgw-multisite-sync-policy-details', 4, zoneToAdd, true);
+ }
+
+ @PageHelper.restrictTo(pages.index.url)
+ deletePipe(pipe_id: string) {
+ cy.get('cd-rgw-multisite-sync-policy-details').should('exist');
+ this.getTab('Pipe').should('exist');
+ this.getTab('Pipe').click();
+ cy.get('cd-rgw-multisite-sync-policy-details').within(() => {
+ cy.get('.datatable-body-cell-label').should('contain', pipe_id);
+ cy.get('[aria-label=search]').first().clear({ force: true }).type(pipe_id);
+ });
+
+ const getRow = this.getTableCellWithContent.bind(this);
+ getRow('cd-rgw-multisite-sync-policy-details', pipe_id).click();
+
+ cy.get('cd-rgw-multisite-sync-policy-details').within(() => {
+ cy.get('.table-actions button.dropdown-toggle').first().click(); // open submenu
+ cy.get(`button.delete`).first().click();
+ });
+
+ cy.get('cd-modal .custom-control-label').click();
+ cy.get('[aria-label="Delete Pipe"]').click();
+ cy.get('cd-modal').should('not.exist');
+
+ cy.get('cd-rgw-multisite-sync-policy-details')
+ .first()
+ .within(() => {
+ cy.get('[aria-label=search]').first().clear({ force: true }).type(pipe_id);
+ });
+ // Waits for item to be removed from table
+ getRow(pipe_id).should('not.exist');
+ }
}
type="text"
i18n-placeholder
placeholder="Bucket Name..."
- formControlName="bucket_name"
- [readonly]="true"/>
+ formControlName="bucket_name"/>
<span
class="invalid-feedback"
*ngIf="currentFormGroupContext.showError('bucket_name', frm, 'bucketNameNotAllowed')"
this.createDirectionalFlowForm();
this.currentFormGroupContext = _.cloneDeep(this.syncPolicyDirectionalFlowForm);
}
-
+ this.currentFormGroupContext.get('bucket_name').disable();
if (this.editing) {
this.currentFormGroupContext.patchValue({
flow_id: this.flowSelectedRow.id,
this.currentFormGroupContext.setErrors({ cdSubmitButton: true });
return;
}
- this.rgwMultisiteService.createEditSyncFlow(this.currentFormGroupContext.value).subscribe(
- () => {
- this.notificationService.show(
- NotificationType.success,
- $localize`Created Sync Flow '${this.currentFormGroupContext.getValue('flow_id')}'`
- );
- this.activeModal.close('success');
- },
- () => {
- // Reset the 'Submit' button.
- this.currentFormGroupContext.setErrors({ cdSubmitButton: true });
- this.activeModal.dismiss();
- }
- );
+ this.rgwMultisiteService
+ .createEditSyncFlow(this.currentFormGroupContext.getRawValue())
+ .subscribe(
+ () => {
+ const action = this.editing ? 'Modified' : 'Created';
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`${action} Sync Flow '${this.currentFormGroupContext.getValue('flow_id')}'`
+ );
+ this.activeModal.close('success');
+ },
+ () => {
+ // Reset the 'Submit' button.
+ this.currentFormGroupContext.setErrors({ cdSubmitButton: true });
+ this.activeModal.dismiss();
+ }
+ );
}
}
--- /dev/null
+<cd-modal [modalRef]="activeModal">
+ <ng-container
+ i18n="form title"
+ class="modal-title">{{ action | titlecase }} Pipe</ng-container>
+
+ <ng-container class="modal-content">
+ <form
+ name="pipeForm"
+ #frm="ngForm"
+ [formGroup]="pipeForm"
+ novalidate>
+ <div class="modal-body">
+ <div class="form-group row">
+ <label
+ class="cd-col-form-label required"
+ for="pipe_id"
+ i18n>Name</label>
+ <div class="cd-col-form-input">
+ <input
+ class="form-control"
+ type="text"
+ placeholder="Pipe Name..."
+ id="pipe_id"
+ name="pipe_id"
+ formControlName="pipe_id"
+ [readonly]="editing"/>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label
+ class="cd-col-form-label required"
+ for="source_zone"
+ i18n>Source Zone </label>
+ <div class="cd-col-form-input">
+ <ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'source_zones', zone: sourceZones }"></ng-container>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label
+ class="cd-col-form-label required"
+ for="destination_zone"
+ i18n>Destination Zone</label>
+ <div class="cd-col-form-input">
+ <ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'destination_zones', zone: destZones }"></ng-container>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label
+ class="cd-col-form-label"
+ for="bucket"
+ i18n>Bucket Name</label>
+ <div class="cd-col-form-input">
+ <input
+ id="bucket"
+ name="bucket"
+ class="form-control"
+ type="text"
+ i18n-placeholder
+ placeholder="Bucket Name..."
+ formControlName="bucket_name"/>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label
+ class="cd-col-form-label"
+ for="source_bucket"
+ i18n>Source Bucket</label>
+ <div class="cd-col-form-input">
+ <input
+ id="source_bucket"
+ name="source_bucket"
+ class="form-control"
+ type="text"
+ i18n-placeholder
+ placeholder="Source Bucket Name..."
+ formControlName="source_bucket"/>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label
+ class="cd-col-form-label"
+ for="dest_bucket"
+ i18n>Destination Bucket</label>
+ <div class="cd-col-form-input">
+ <input
+ id="dest_bucket"
+ name="dest_bucket"
+ class="form-control"
+ type="text"
+ i18n-placeholder
+ placeholder="Destination Bucket Name..."
+ formControlName="destination_bucket"/>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <cd-form-button-panel
+ (submitActionEvent)="submit()"
+ [form]="pipeForm"
+ [submitText]="(action | titlecase) + ' ' + 'Pipe'">
+ </cd-form-button-panel>
+ </div>
+ </form>
+ </ng-container>
+</cd-modal>
+
+<ng-template
+ #zoneMultiSelect
+ let-name="name"
+ let-zone="zone">
+ <cd-select-badges
+ id="{{ name }}"
+ name="{{ name }}"
+ [customBadges]="zone.customBadges"
+ [customBadgeValidators]="zone.data.validators"
+ [messages]="zone.data.messages"
+ [data]="zone.data.selected"
+ [options]="zone.data.available"
+ (selection)="onZoneSelection(name)">
+ </cd-select-badges>
+ <i
+ *ngIf="zone.data.selected.length <= 0"
+ i18n-title
+ title="Pipe should be associated with {{ name }}"
+ class="{{ icons.warning }} icon-warning-color">
+ </i>
+ <span
+ class="invalid-feedback"
+ *ngIf="pipeForm.showError(name, frm, 'required')"
+ i18n>{{ name }} selection is required!
+ </span>
+</ng-template>
--- /dev/null
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwMultisiteSyncPipeModalComponent } from './rgw-multisite-sync-pipe-modal.component';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ToastrModule } from 'ngx-toastr';
+import { PipesModule } from '~/app/shared/pipes/pipes.module';
+import { ReactiveFormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+
+describe('RgwMultisiteSyncPipeModalComponent', () => {
+ let component: RgwMultisiteSyncPipeModalComponent;
+ let fixture: ComponentFixture<RgwMultisiteSyncPipeModalComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [RgwMultisiteSyncPipeModalComponent],
+ imports: [
+ HttpClientTestingModule,
+ ToastrModule.forRoot(),
+ PipesModule,
+ ReactiveFormsModule,
+ CommonModule
+ ],
+ providers: [NgbActiveModal]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(RgwMultisiteSyncPipeModalComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+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 { SelectOption } from '~/app/shared/components/select/select-option.model';
+import { catchError, switchMap } from 'rxjs/operators';
+import { of } from 'rxjs';
+import { RgwDaemon } from '../models/rgw-daemon';
+import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
+import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import _ from 'lodash';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
+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';
+
+@Component({
+ selector: 'cd-rgw-multisite-sync-pipe-modal',
+ templateUrl: './rgw-multisite-sync-pipe-modal.component.html',
+ styleUrls: ['./rgw-multisite-sync-pipe-modal.component.scss']
+})
+export class RgwMultisiteSyncPipeModalComponent implements OnInit {
+ groupExpandedRow: any;
+ pipeSelectedRow: any;
+ pipeForm: CdFormGroup;
+ action: string;
+ editing: boolean;
+ sourceZones = new ZoneData(false, 'Filter Zones');
+ destZones = new ZoneData(true, 'Filter or Add Zones');
+ icons = Icons;
+
+ constructor(
+ public activeModal: NgbActiveModal,
+ private rgwDaemonService: RgwDaemonService,
+ private rgwZonegroupService: RgwZonegroupService,
+ private rgwMultisiteService: RgwMultisiteService,
+ private notificationService: NotificationService
+ ) {}
+
+ ngOnInit(): void {
+ this.editing = this.action === 'create' ? false : true;
+ this.pipeForm = new CdFormGroup({
+ pipe_id: new UntypedFormControl('', {
+ validators: [Validators.required]
+ }),
+ group_id: new UntypedFormControl(this.groupExpandedRow?.groupName || '', {
+ validators: [Validators.required]
+ }),
+ bucket_name: new UntypedFormControl(this.groupExpandedRow?.bucket || ''),
+ source_bucket: new UntypedFormControl(''),
+ source_zones: new UntypedFormControl('', {
+ validators: [Validators.required]
+ }),
+ destination_bucket: new UntypedFormControl(''),
+ destination_zones: new UntypedFormControl('', {
+ validators: [Validators.required]
+ })
+ });
+ this.pipeForm.get('bucket_name').disable();
+ this.rgwDaemonService.selectedDaemon$
+ .pipe(
+ switchMap((daemon: RgwDaemon) => {
+ if (daemon) {
+ const zonegroupObj = new RgwZonegroup();
+ zonegroupObj.name = daemon.zonegroup_name;
+ return this.rgwZonegroupService.get(zonegroupObj).pipe(
+ catchError(() => {
+ return of([]);
+ })
+ );
+ } else {
+ return of([]);
+ }
+ })
+ )
+ .subscribe((zonegroupData: any) => {
+ if (zonegroupData && zonegroupData?.zones?.length > 0) {
+ let zones: any[] = [];
+ zonegroupData.zones.forEach((zone: any) => {
+ zones.push(new SelectOption(false, zone.name, ''));
+ });
+ this.sourceZones.data.available = [...zones];
+ if (this.editing) {
+ this.sourceZones.data.selected = this.pipeSelectedRow.source.zones;
+ this.destZones.data.selected = this.pipeSelectedRow.dest.zones;
+ this.pipeForm.patchValue({
+ pipe_id: this.pipeSelectedRow.id,
+ source_zones: this.pipeSelectedRow.source.zones,
+ destination_zones: this.pipeSelectedRow.dest.zones,
+ source_bucket: this.pipeSelectedRow.source.bucket,
+ destination_bucket: this.pipeSelectedRow.dest.bucket
+ });
+ }
+ }
+ });
+ }
+
+ onZoneSelection(zoneType: string) {
+ if (zoneType === 'source_zones') {
+ this.pipeForm.patchValue({
+ source_zones: this.sourceZones.data.selected
+ });
+ } else {
+ this.pipeForm.patchValue({
+ destination_zones: this.destZones.data.selected
+ });
+ }
+ }
+
+ submit() {
+ if (this.pipeForm.invalid) {
+ return;
+ }
+ // Ensure that no validation is pending
+ if (this.pipeForm.pending) {
+ 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();
+ }
+ );
+ }
+}
[maxLimit]="25"
[toolHeader]="true"
(updateSelection)="updateSelection($event, flowType.symmetrical)"
- (fetchData)="loadFlowData($event)">
+ (fetchData)="loadData($event)">
<div class="table-actions btn-toolbar">
<cd-table-actions
[permission]="permission"
[maxLimit]="25"
[toolHeader]="true"
(updateSelection)="updateSelection($event, flowType.directional)"
- (fetchData)="loadFlowData($event)">
+ (fetchData)="loadData($event)">
<div class="table-actions btn-toolbar">
<cd-table-actions
[permission]="permission"
</div>
</cd-table>
<cd-alert-panel
+ *ngIf="dirFlowSelection.hasSelection"
type="info"
- *ngIf="dirFlowSelection.hasSelection">
- 'Edit' and 'Delete' functionalities for Directional flow are disabled for now due to some internal dependency. They will be enabled once the issue is resolved.
+ 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">
+ <a ngbNavLink
+ i18n>Pipe</a>
+ <ng-template ngbNavContent>
+ <legend i18n>
+ Pipe
+ <cd-help-text>
+ A pipe defines the actual buckets that can use these data flows, and the properties that are associated with it.
+ </cd-help-text>
+ </legend>
+ <cd-table
+ #table
+ [data]="pipeData"
+ [columns]="pipeCols"
+ selectionType="multiClick"
+ [searchableObjects]="true"
+ [hasDetails]="false"
+ [serverSide]="false"
+ [toolHeader]="true"
+ (updateSelection)="pipeSelection = $event"
+ (fetchData)="loadData($event)">
+ <div class="table-actions btn-toolbar">
+ <cd-table-actions
+ [permission]="permission"
+ [selection]="pipeSelection"
+ class="btn-group"
+ [tableActions]="pipeTableActions">
+ </cd-table-actions>
+ </div>
+ </cd-table>
+ </ng-template>
+ </ng-container>
</nav>
-
<div [ngbNavOutlet]="nav"></div>
</ng-container>
import { TableComponent } from '~/app/shared/datatable/table/table.component';
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';
@Component({
selector: 'cd-rgw-multisite-sync-policy-details',
modalRef: NgbModalRef;
symmetricalFlowData: any = [];
directionalFlowData: any = [];
+ pipeData: any = [];
symmetricalFlowCols: CdTableColumn[];
directionalFlowCols: CdTableColumn[];
+ pipeCols: CdTableColumn[];
symFlowTableActions: CdTableAction[];
dirFlowTableActions: CdTableAction[];
+ pipeTableActions: CdTableAction[];
symFlowSelection = new CdTableSelection();
dirFlowSelection = new CdTableSelection();
+ pipeSelection = new CdTableSelection();
constructor(
private actionLabels: ActionLabelsI18n,
flexGrow: 1
}
];
+ this.pipeCols = [
+ {
+ name: 'Name',
+ prop: 'id',
+ flexGrow: 1
+ },
+ {
+ name: 'Source Zone',
+ prop: 'source.zones',
+ flexGrow: 1
+ },
+ {
+ name: 'Destination Zone',
+ prop: 'dest.zones',
+ flexGrow: 1
+ },
+ {
+ name: 'Source Bucket',
+ prop: 'source.bucket',
+ flexGrow: 1
+ },
+ {
+ name: 'Destination Bucket',
+ prop: 'dest.bucket',
+ flexGrow: 1
+ }
+ ];
const symAddAction: CdTableAction = {
permission: 'create',
icon: Icons.add,
canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection
};
this.dirFlowTableActions = [dirAddAction, dirEditAction, dirDeleteAction];
+ const pipeAddAction: CdTableAction = {
+ permission: 'create',
+ icon: Icons.add,
+ name: this.actionLabels.CREATE,
+ click: () => this.openPipeModal(),
+ canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
+ };
+ const pipeEditAction: CdTableAction = {
+ permission: 'update',
+ icon: Icons.edit,
+ name: this.actionLabels.EDIT,
+ click: () => this.openPipeModal(true)
+ };
+ const pipeDeleteAction: CdTableAction = {
+ permission: 'delete',
+ icon: Icons.destroy,
+ disable: () => !this.pipeSelection.hasSelection,
+ name: this.actionLabels.DELETE,
+ click: () => this.deletePipe(),
+ canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection
+ };
+ this.pipeTableActions = [pipeAddAction, pipeEditAction, pipeDeleteAction];
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.expandedRow.currentValue && changes.expandedRow.currentValue.groupName) {
this.symmetricalFlowData = [];
this.directionalFlowData = [];
- this.loadFlowData();
+ this.loadData();
}
}
- loadFlowData(context?: any) {
+ loadData(context?: any) {
if (this.expandedRow) {
this.rgwMultisiteService
.getSyncPolicyGroup(this.expandedRow.groupName, this.expandedRow.bucket)
(policy: any) => {
this.symmetricalFlowData = policy.data_flow[FlowType.symmetrical] || [];
this.directionalFlowData = policy.data_flow[FlowType.directional] || [];
+ this.pipeData = policy.pipes || [];
},
() => {
if (context) {
try {
const res = await this.modalRef.result;
if (res === 'success') {
- this.loadFlowData();
+ this.loadData();
}
} catch (err) {}
}
}
});
}
+
+ async openPipeModal(edit = false) {
+ const action = edit ? 'edit' : 'create';
+ const initialState = {
+ groupExpandedRow: this.expandedRow,
+ pipeSelectedRow: this.pipeSelection.first(),
+ action: action
+ };
+
+ this.modalRef = this.modalService.show(RgwMultisiteSyncPipeModalComponent, initialState, {
+ size: 'lg'
+ });
+
+ try {
+ const res = await this.modalRef.result;
+ if (res === 'success') {
+ this.loadData();
+ }
+ } catch (err) {}
+ }
+
+ deletePipe() {
+ const pipeIds = this.pipeSelection.selected.map((pipe: any) => pipe.id);
+ this.modalService.show(CriticalConfirmationModalComponent, {
+ itemDescription: this.pipeSelection.hasSingleSelection ? $localize`Pipe` : $localize`Pipes`,
+ itemNames: pipeIds,
+ bodyTemplate: this.deleteTpl,
+ submitActionObservable: () => {
+ return new Observable((observer: Subscriber<any>) => {
+ this.taskWrapper
+ .wrapTaskAroundCall({
+ task: new FinishedTask('rgw/multisite/sync-pipe/delete', {
+ pipe_ids: pipeIds
+ }),
+ call: observableForkJoin(
+ this.pipeSelection.selected.map((pipe: any) => {
+ return this.rgwMultisiteService.removeSyncPipe(
+ pipe.id,
+ this.expandedRow.groupName,
+ this.expandedRow.bucket
+ );
+ })
+ )
+ })
+ .subscribe({
+ error: (error: any) => {
+ // Forward the error to the observer.
+ observer.error(error);
+ // Reload the data table content because some deletions might
+ // have been executed successfully in the meanwhile.
+ this.table.refreshBtn();
+ },
+ complete: () => {
+ // Notify the observer that we are done.
+ observer.complete();
+ // Reload the data table content.
+ this.table.refreshBtn();
+ }
+ });
+ });
+ }
+ });
+ }
}
import { RgwMultisiteSyncPolicyFormComponent } from './rgw-multisite-sync-policy-form/rgw-multisite-sync-policy-form.component';
import { RgwMultisiteSyncPolicyDetailsComponent } from './rgw-multisite-sync-policy-details/rgw-multisite-sync-policy-details.component';
import { RgwMultisiteSyncFlowModalComponent } from './rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component';
+import { RgwMultisiteSyncPipeModalComponent } from './rgw-multisite-sync-pipe-modal/rgw-multisite-sync-pipe-modal.component';
@NgModule({
imports: [
RgwMultisiteSyncPolicyComponent,
RgwMultisiteSyncPolicyFormComponent,
RgwMultisiteSyncPolicyDetailsComponent,
- RgwMultisiteSyncFlowModalComponent
+ RgwMultisiteSyncFlowModalComponent,
+ RgwMultisiteSyncPipeModalComponent
],
providers: [TitleCasePipe]
})
expect(req.request.method).toBe('DELETE');
req.flush(null);
});
+
+ it('should create Sync Pipe', () => {
+ const payload = {
+ pipe_id: 'test',
+ bucket_name: 'test',
+ source_zones: ['zone1-zg1-realm1'],
+ destination_zones: ['zone1-zg2-realm2'],
+ group_id: 'sync-grp'
+ };
+ service.createEditSyncPipe(payload).subscribe();
+ const req = httpTesting.expectOne('api/rgw/multisite/sync-pipe');
+ expect(req.request.method).toBe('PUT');
+ expect(req.request.body).toEqual(payload);
+ req.flush(null);
+ });
+
+ it('should edit Symmetrical Sync flow', () => {
+ const payload = {
+ pipe_id: 'test',
+ bucket_name: 'test',
+ source_zones: ['zone1-zg1-realm1'],
+ destination_zones: ['zone1-zg2-realm2', 'zone2-zg1-realm1'],
+ group_id: 'sync-grp'
+ };
+ service.createEditSyncFlow(payload).subscribe();
+ const req = httpTesting.expectOne('api/rgw/multisite/sync-flow');
+ expect(req.request.method).toBe('PUT');
+ expect(req.request.body).toEqual(payload);
+ req.flush(null);
+ });
+
+ it('should remove Sync Pipe', () => {
+ service.removeSyncPipe('test', 'sync-grp', 'new-bucket').subscribe();
+ const req = httpTesting.expectOne(
+ `api/rgw/multisite/sync-pipe/sync-grp/test?bucket_name=new-bucket`
+ );
+ expect(req.request.method).toBe('DELETE');
+ req.flush(null);
+ });
});
{ params }
);
}
+
+ createEditSyncPipe(payload: any) {
+ return this.http.put(`${this.url}/sync-pipe`, payload);
+ }
+
+ removeSyncPipe(pipe_id: string, group_id: string, bucket_name?: string) {
+ let params = new HttpParams();
+ if (bucket_name) {
+ params = params.append('bucket_name', encodeURIComponent(bucket_name));
+ }
+ return this.http.delete(
+ `${this.url}/sync-pipe/${encodeURIComponent(group_id)}/${encodeURIComponent(pipe_id)}`,
+ { params }
+ );
+ }
}
this.commonOperations.delete,
(metadata) => {
return $localize`${
- metadata.flow_ids.length > 1
- ? 'selected Flow Names'
- : `Flow Name '${metadata.flow_ids[0]}'`
+ metadata.flow_ids.length > 1 ? 'selected Flow' : `Flow '${metadata.flow_ids[0]}'`
+ }`;
+ }
+ ),
+ 'rgw/multisite/sync-pipe/delete': this.newTaskMessage(
+ this.commonOperations.delete,
+ (metadata) => {
+ return $localize`${
+ metadata.pipe_ids.length > 1 ? 'selected pipe' : `Pipe '${metadata.pipe_ids[0]}'`
}`;
}
),
bucket_name:
default: ''
type: string
- destination_buckets:
+ destination_bucket:
+ default: ''
type: string
destination_zones:
type: string
type: string
pipe_id:
type: string
+ source_bucket:
+ default: ''
+ type: string
source_zones:
type: string
required:
name: destination_zones
schema:
type: string
- - allowEmptyValue: true
+ - default: ''
in: query
- name: destination_buckets
+ name: destination_bucket
schema:
type: string
- default: ''
def create_sync_pipe(self, group_id: str, pipe_id: str,
source_zones: Optional[List[str]] = None,
destination_zones: Optional[List[str]] = None,
- destination_buckets: Optional[List[str]] = None, bucket_name: str = ''):
+ 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 destination_zones:
rgw_sync_policy_cmd += ['--dest-zones', ','.join(destination_zones)]
- if destination_buckets:
- rgw_sync_policy_cmd += ['--dest-bucket', ','.join(destination_buckets)]
+ if source_bucket:
+ rgw_sync_policy_cmd += ['--source-bucket', source_bucket]
+
+ if destination_bucket:
+ rgw_sync_policy_cmd += ['--dest-bucket', destination_bucket]
try:
exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
def remove_sync_pipe(self, group_id: str, pipe_id: str,
source_zones: Optional[List[str]] = None,
destination_zones: Optional[List[str]] = None,
- destination_buckets: Optional[List[str]] = None, bucket_name: str = ''):
+ destination_bucket: str = '', bucket_name: str = ''):
rgw_sync_policy_cmd = ['sync', 'group', 'pipe', 'remove',
'--group-id', group_id, '--pipe-id', pipe_id]
if destination_zones:
rgw_sync_policy_cmd += ['--dest-zones', ','.join(destination_zones)]
- if destination_buckets:
- rgw_sync_policy_cmd += ['--dest-bucket', ','.join(destination_buckets)]
+ if destination_bucket:
+ rgw_sync_policy_cmd += ['--dest-bucket', destination_bucket]
try:
exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
zones=zone_names)
# create a sync pipe with source and destination zones
self.create_sync_pipe(_SYNC_GROUP_ID, _SYNC_PIPE_ID, source_zones=['*'],
- destination_zones=['*'], destination_buckets=['*'])
+ destination_zones=['*'], source_bucket='*', destination_bucket='*')
# period update --commit
self.update_period()