Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mgr/dashboard: RBD copy, RBD flatten and snapshot clone (frontend) #21526

Merged
merged 7 commits into from Apr 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions qa/tasks/mgr/dashboard/test_rbd.py
Expand Up @@ -59,6 +59,10 @@ def edit_image(cls, pool, image, name=None, size=None, features=None):
return cls._task_put('/api/block/image/{}/{}'.format(pool, image),
{'name': name, 'size': size, 'features': features})

@classmethod
def flatten_image(cls, pool, image):
return cls._task_post('/api/block/image/{}/{}/flatten'.format(pool, image))

@classmethod
def create_snapshot(cls, pool, image, snapshot):
return cls._task_post('/api/block/image/{}/{}/snap'.format(pool, image),
Expand Down Expand Up @@ -510,3 +514,32 @@ def test_copy(self):
self.assertStatus(204)
self.remove_image('rbd_iscsi', 'coimg-copy')
self.assertStatus(204)

def test_flatten(self):
self.create_snapshot('rbd', 'img1', 'snapf')
self.update_snapshot('rbd', 'img1', 'snapf', None, True)
self.clone_image('rbd', 'img1', 'snapf', 'rbd_iscsi', 'img1_snapf_clone')

img = self._get('/api/block/image/rbd_iscsi/img1_snapf_clone')
self.assertStatus(200)
self.assertIsNotNone(img['parent'])

self.flatten_image('rbd_iscsi', 'img1_snapf_clone')
self.assertStatus(200)

img = self._get('/api/block/image/rbd_iscsi/img1_snapf_clone')
self.assertStatus(200)
self.assertIsNone(img['parent'])

self.update_snapshot('rbd', 'img1', 'snapf', None, False)
self.remove_snapshot('rbd', 'img1', 'snapf')
self.assertStatus(204)

self.remove_image('rbd_iscsi', 'img1_snapf_clone')
self.assertStatus(204)

def test_default_features(self):
default_features = self._get('/api/block/image/default_features')
self.assertEqual(default_features, ['deep-flatten', 'exclusive-lock',
'fast-diff', 'layering',
'object-map'])
19 changes: 18 additions & 1 deletion src/pybind/mgr/dashboard/controllers/rbd.py
Expand Up @@ -329,7 +329,7 @@ def _edit(ioctx, image):
'dest_image_name': '{dest_image_name}'}, 2.0)
@RESTController.resource(['POST'])
def copy(self, pool_name, image_name, dest_pool_name, dest_image_name,
obj_size=None, features=None, stripe_unit=None,
snapshot_name=None, obj_size=None, features=None, stripe_unit=None,
stripe_count=None, data_pool=None):

def _src_copy(s_ioctx, s_img):
Expand All @@ -342,13 +342,30 @@ def _copy(d_ioctx):
# Set features
feature_bitmask = _format_features(features)

if snapshot_name:
s_img.set_snap(snapshot_name)

s_img.copy(d_ioctx, dest_image_name, feature_bitmask, l_order,
stripe_unit, stripe_count, data_pool)

return _rbd_call(dest_pool_name, _copy)

return _rbd_image_call(pool_name, image_name, _src_copy)

@RbdTask('flatten', ['{pool_name}', '{image_name}'], 2.0)
@RESTController.resource(['POST'])
def flatten(self, pool_name, image_name):

def _flatten(ioctx, image):
image.flatten()

return _rbd_image_call(pool_name, image_name, _flatten)

@RESTController.collection(['GET'])
def default_features(self):
rbd_default_features = mgr.get('config')['rbd_default_features']
return _format_bitmask(int(rbd_default_features))


@ApiController('block/image/:pool_name/:image_name/snap')
@AuthRequired()
Expand Down
15 changes: 15 additions & 0 deletions src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
Expand Up @@ -49,6 +49,21 @@ const routes: Routes = [
{ path: 'rbd/add', component: RbdFormComponent, canActivate: [AuthGuardService] },
{ path: 'rbd/edit/:pool/:name', component: RbdFormComponent, canActivate: [AuthGuardService] },
{ path: 'pool', component: PoolListComponent, canActivate: [AuthGuardService] },
{
path: 'rbd/clone/:pool/:name/:snap',
component: RbdFormComponent,
canActivate: [AuthGuardService]
},
{
path: 'rbd/copy/:pool/:name',
component: RbdFormComponent,
canActivate: [AuthGuardService]
},
{
path: 'rbd/copy/:pool/:name/:snap',
component: RbdFormComponent,
canActivate: [AuthGuardService]
},
{
path: 'perf_counters/:type/:id',
component: PerformanceCounterComponent,
Expand Down
Expand Up @@ -7,6 +7,9 @@ import { BsDropdownModule, ModalModule, TabsModule, TooltipModule } from 'ngx-bo
import { ProgressbarModule } from 'ngx-bootstrap/progressbar';

import { SharedModule } from '../../shared/shared.module';
import {
FlattenConfirmationModalComponent
} from './flatten-confirmation-modal/flatten-confimation-modal.component';
import { IscsiComponent } from './iscsi/iscsi.component';
import { MirrorHealthColorPipe } from './mirror-health-color.pipe';
import { MirroringComponent } from './mirroring/mirroring.component';
Expand All @@ -23,7 +26,8 @@ import {
entryComponents: [
RbdDetailsComponent,
RbdSnapshotFormComponent,
RollbackConfirmationModalComponent
RollbackConfirmationModalComponent,
FlattenConfirmationModalComponent
],
imports: [
CommonModule,
Expand All @@ -46,7 +50,8 @@ import {
RbdFormComponent,
RbdSnapshotListComponent,
RbdSnapshotFormComponent,
RollbackConfirmationModalComponent
RollbackConfirmationModalComponent,
FlattenConfirmationModalComponent
]
})
export class BlockModule { }
@@ -0,0 +1,30 @@
<div class="modal-header">
<h4 i18n
class="modal-title pull-left">RBD flatten</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form name="flattenForm"
class="form-horizontal"
#formDir="ngForm"
[formGroup]="flattenForm"
novalidate>
<div class="modal-body">
You are about to flatten <strong>{{ child }}</strong>.
<br>
<br>
All blocks will be copied from parent <strong>{{ parent }}</strong> to child <strong>{{ child }}</strong>.
</div>
<div class="modal-footer">
<div class="button-group text-right">
<cd-submit-button i18n
[form]="flattenForm"
(submitAction)="submit()">
Flatten
</cd-submit-button>
<button i18n type="button" class="btn btn-sm btn-default" (click)="modalRef.hide()">Cancel</button>
</div>
</div>
</form>

@@ -0,0 +1,42 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';

import { ToastModule } from 'ng2-toastr';
import { BsModalRef, BsModalService } from 'ngx-bootstrap';

import { ApiModule } from '../../../shared/api/api.module';
import { ServicesModule } from '../../../shared/services/services.module';
import { SharedModule } from '../../../shared/shared.module';
import { FlattenConfirmationModalComponent } from './flatten-confimation-modal.component';

describe('FlattenConfirmationModalComponent', () => {
let component: FlattenConfirmationModalComponent;
let fixture: ComponentFixture<FlattenConfirmationModalComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
HttpClientTestingModule,
SharedModule,
ServicesModule,
ApiModule,
ToastModule.forRoot()
],
declarations: [ FlattenConfirmationModalComponent ],
providers: [ BsModalRef, BsModalService ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(FlattenConfirmationModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,36 @@
import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { BsModalRef } from 'ngx-bootstrap';
import { Subject } from 'rxjs/Subject';

@Component({
selector: 'cd-flatten-confimation-modal',
templateUrl: './flatten-confimation-modal.component.html',
styleUrls: ['./flatten-confimation-modal.component.scss']
})
export class FlattenConfirmationModalComponent implements OnInit {

child: string;
parent: string;

flattenForm: FormGroup;

public onSubmit: Subject<string>;

constructor(public modalRef: BsModalRef) {
this.createForm();
}

createForm() {
this.flattenForm = new FormGroup({});
}

ngOnInit() {
this.onSubmit = new Subject();
}

submit() {
this.onSubmit.next();
}
}
@@ -1,4 +1,5 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';

import { TabsModule, TooltipModule } from 'ngx-bootstrap';

Expand All @@ -13,7 +14,7 @@ describe('RbdDetailsComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RbdDetailsComponent, RbdSnapshotListComponent ],
imports: [ SharedModule, TabsModule.forRoot(), TooltipModule.forRoot(), ]
imports: [ SharedModule, TabsModule.forRoot(), TooltipModule.forRoot(), RouterTestingModule]
})
.compileComponents();
}));
Expand Down
@@ -0,0 +1,9 @@
export class RbdFormCloneRequestModel {
child_pool_name: string;
child_image_name: string;
obj_size: number;
features: Array<string> = [];
stripe_unit: number;
stripe_count: number;
data_pool: string;
}
@@ -0,0 +1,10 @@
export class RbdFormCopyRequestModel {
dest_pool_name: string;
dest_image_name: string;
snapshot_name: string;
obj_size: number;
features: Array<string> = [];
stripe_unit: number;
stripe_count: number;
data_pool: string;
}
@@ -0,0 +1,5 @@
export enum RbdFormMode {
editing = 'editing',
cloning = 'cloning',
copying = 'copying'
}
@@ -1,5 +1,7 @@
import { RbdFormModel } from './rbd-form.model';
import { RbdParentModel } from './rbd-parent.model';

export class RbdFormResponseModel extends RbdFormModel {
features_name: string[];
parent: RbdParentModel;
}
Expand Up @@ -4,7 +4,7 @@
<li class="breadcrumb-item">
<a routerLink="/block/rbd">Images</a></li>
<li class="breadcrumb-item active"
i18n>{editing, select, 1 {Edit} other {Add}}</li>
i18n>{mode, select, editing {Edit} cloning {Clone} copying {Copy} other {Add}}</li>
</ol>
</nav>

Expand All @@ -17,11 +17,28 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<span i18n>{editing, select, 1 {Edit} other {Add}}</span> RBD
<span i18n>{mode, select, editing {Edit} cloning {Clone} copying {Copy} other {Add}}</span> RBD
</h3>
</div>
<div class="panel-body">

<!-- Parent -->
<div class="form-group"
*ngIf="rbdForm.controls.parent.value">
<label i18n
class="control-label col-sm-3"
for="name">{mode, select, cloning {Clone from} copying {Copy from} other {Parent}}
</label>
<div class="col-sm-9">
<input class="form-control"
type="text"
id="parent"
name="parent"
formControlName="parent">
<hr>
</div>
</div>

<!-- Name -->
<div class="form-group"
[ngClass]="{'has-error': (formDir.submitted || rbdForm.controls.name.dirty) && rbdForm.controls.name.invalid}">
Expand Down Expand Up @@ -175,27 +192,15 @@ <h3 class="panel-title">
</label>
<div class="col-sm-9">
<div class="checkbox checkbox-primary"
*ngIf="!editing">
*ngFor="let feature of featuresList">
<input type="checkbox"
id="default-features"
name="default-features"
formControlName="defaultFeatures">
<label i18n
for="default-features">Use default features</label>
</div>
<div *ngIf="!featuresFormGroups.value.defaultFeatures">
<br *ngIf="!editing">
<div class="checkbox checkbox-primary"
*ngFor="let feature of featuresList">
<input type="checkbox"
id="{{ feature.key }}"
name="{{ feature.key }}"
formControlName="{{ feature.key }}">
<label for="{{ feature.key }}">{{ feature.desc }}</label>
<cd-helper *ngIf="feature.helperHtml"
html="{{ feature.helperHtml }}">
</cd-helper>
</div>
id="{{ feature.key }}"
name="{{ feature.key }}"
formControlName="{{ feature.key }}">
<label for="{{ feature.key }}">{{ feature.desc }}</label>
<cd-helper *ngIf="feature.helperHtml"
html="{{ feature.helperHtml }}">
</cd-helper>
</div>
</div>
</div>
Expand Down Expand Up @@ -299,7 +304,7 @@ <h3 class="panel-title">
<cd-submit-button [form]="rbdForm"
type="button"
(submitAction)="submit()">
<span i18n>{editing, select, 1 {Update} other {Create}}</span> RBD
<span i18n>{mode, select, editing {Update} cloning {Clone} copying {Copy} other {Create}}</span> RBD
</cd-submit-button>
<button i18n
type="button"
Expand Down