Skip to content

Commit

Permalink
Add RGW management features.
Browse files Browse the repository at this point in the history
Signed-off-by: Volker Theile <vtheile@suse.com>
  • Loading branch information
votdev committed Apr 12, 2018
1 parent 23b78f7 commit d75bc5d
Show file tree
Hide file tree
Showing 44 changed files with 3,305 additions and 35 deletions.
9 changes: 9 additions & 0 deletions src/pybind/mgr/dashboard/controllers/rgw.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,12 @@ def __call__(self, path, **params):
except RequestException as e:
cherrypy.response.status = e.status_code
return e.content


@ApiController('rgw/bucket')
class RgwBucket(RESTController):

@RESTController.args_from_json
def create(self, bucket, uid):
rgw_client = RgwClient.instance(uid)
return rgw_client.create_bucket(bucket)
22 changes: 22 additions & 0 deletions src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import { DashboardComponent } from './ceph/dashboard/dashboard/dashboard.compone
import {
PerformanceCounterComponent
} from './ceph/performance-counter/performance-counter/performance-counter.component';
import { RgwBucketFormComponent } from './ceph/rgw/rgw-bucket-form/rgw-bucket-form.component';
import { RgwBucketListComponent } from './ceph/rgw/rgw-bucket-list/rgw-bucket-list.component';
import { RgwDaemonListComponent } from './ceph/rgw/rgw-daemon-list/rgw-daemon-list.component';
import { RgwUserFormComponent } from './ceph/rgw/rgw-user-form/rgw-user-form.component';
import { RgwUserListComponent } from './ceph/rgw/rgw-user-list/rgw-user-list.component';
import { LoginComponent } from './core/auth/login/login.component';
import { NotFoundComponent } from './core/not-found/not-found.component';
Expand All @@ -37,11 +39,31 @@ const routes: Routes = [
component: RgwUserListComponent,
canActivate: [AuthGuardService]
},
{
path: 'rgw/user/add',
component: RgwUserFormComponent,
canActivate: [AuthGuardService]
},
{
path: 'rgw/user/edit/:uid',
component: RgwUserFormComponent,
canActivate: [AuthGuardService]
},
{
path: 'rgw/bucket',
component: RgwBucketListComponent,
canActivate: [AuthGuardService]
},
{
path: 'rgw/bucket/add',
component: RgwBucketFormComponent,
canActivate: [AuthGuardService]
},
{
path: 'rgw/bucket/edit/:bucket',
component: RgwBucketFormComponent,
canActivate: [AuthGuardService]
},
{ path: 'block/iscsi', component: IscsiComponent, canActivate: [AuthGuardService] },
{ path: 'block/pool/:name', component: PoolDetailComponent, canActivate: [AuthGuardService] },
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export class RgwUserCapability {
type: string;
perm: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class RgwUserS3Key {
user: string;
generate_key?: boolean;
access_key: string;
secret_key: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class RgwUserSubuser {
id: string;
permissions: string;
generate_secret?: boolean;
secret_key?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export class RgwUserSwiftKey {
user: string;
secret_key: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li i18n
class="breadcrumb-item">Object Gateway</li>
<li i18n
class="breadcrumb-item">
<a i18n
routerLink="/rgw/bucket">Buckets</a>
</li>
<li class="breadcrumb-item active"
aria-current="page">
{editing, select, 1 {Edit} other {Add}}
</li>
</ol>
</nav>

<div class="col-sm-12 col-lg-6">
<form name="bucketForm"
class="form-horizontal"
#frm="ngForm"
[formGroup]="bucketForm"
novalidate>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{editing, select, 1 {Edit} other {Add}} bucket
</h3>
</div>
<div class="panel-body">

<!-- Id -->
<div class="form-group"
*ngIf="editing">
<label i18n
class="col-sm-3 control-label"
for="id">Id</label>
<div class="col-sm-9">
<input id="id"
name="id"
class="form-control"
type="text"
formControlName="id"
readonly>
</div>
</div>

<!-- Name -->
<div class="form-group"
[ngClass]="{'has-error': (frm.submitted || bucketForm.controls.bucket.dirty) && bucketForm.controls.bucket.invalid}">
<label i18n
class="control-label col-sm-3"
for="bucket">Name
<span class="required"
*ngIf="!editing"></span>
</label>
<div class="col-sm-9">
<input id="bucket"
name="bucket"
class="form-control"
type="text"
placeholder="Name..."
formControlName="bucket"
[readonly]="editing"
autofocus>
<span i18n
class="help-block"
*ngIf="(frm.submitted || bucketForm.controls.bucket.dirty) && bucketForm.controls.bucket.hasError('required')">
This field is required.
</span>
<span i18n
class="help-block"
*ngIf="(frm.submitted || bucketForm.controls.bucket.dirty) && bucketForm.controls.bucket.hasError('bucketNameInvalid')">
The value is not valid.
</span>
<span i18n
class="help-block"
*ngIf="(frm.submitted || bucketForm.controls.bucket.dirty) && bucketForm.controls.bucket.hasError('bucketNameExists')">
The chosen name is already in use.
</span>
</div>
</div>

<!-- Owner -->
<div class="form-group"
[ngClass]="{'has-error': (frm.submitted || bucketForm.controls.owner.dirty) && bucketForm.controls.owner.invalid}">
<label i18n
class="control-label col-sm-3"
for="owner">Owner
<span class="required"></span>
</label>
<div class="col-sm-9">
<select id="owner"
name="owner"
class="form-control"
formControlName="owner">
<option i18n
*ngIf="owners === null"
[ngValue]="null">Loading...
</option>
<option i18n
*ngIf="owners !== null"
[ngValue]="null">-- Select a user --
</option>
<option *ngFor="let owner of owners"
[value]="owner">{{ owner }}</option>
</select>
<span i18n
class="help-block"
*ngIf="(frm.submitted || bucketForm.controls.owner.dirty) && bucketForm.controls.owner.hasError('required')">
This field is required.
</span>
</div>
</div>

</div>
<div class="panel-footer">
<div class="button-group text-right">
<cd-submit-button (submitAction)="submit()"
[form]="bucketForm"
i18n>
{editing, select, 1 {Update} other {Add}}
</cd-submit-button>
<button i18n
type="button"
class="btn btn-sm btn-default"
routerLink="/rgw/bucket">
Back
</button>
</div>
</div>
</div>
</form>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';

import { RgwBucketService } from '../../../shared/api/rgw-bucket.service';
import { RgwUserService } from '../../../shared/api/rgw-user.service';
import { SharedModule } from '../../../shared/shared.module';
import { RgwBucketFormComponent } from './rgw-bucket-form.component';

describe('RgwBucketFormComponent', () => {
let component: RgwBucketFormComponent;
let fixture: ComponentFixture<RgwBucketFormComponent>;
let queryResult: Array<string> = [];

const fakeRgwBucketService = {
exists: () => {
return new Promise((resolve) => {
resolve(queryResult);
});
}
};

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RgwBucketFormComponent ],
imports: [
HttpClientTestingModule,
ReactiveFormsModule,
RouterTestingModule,
SharedModule
],
providers: [
RgwUserService,
{ provide: RgwBucketService, useValue: fakeRgwBucketService }
]
})
.compileComponents();
}));

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

it('should create', () => {
expect(component).toBeTruthy();
});

describe('bucketNameValidator', () => {
it('should validate name (1/4)', () => {
const validatorFn = component.bucketNameValidator();
const ctrl = new FormControl('');
const validatorPromise = validatorFn(ctrl);
expect(validatorPromise instanceof Promise).toBeTruthy();
if (validatorPromise instanceof Promise) {
validatorPromise.then((resp) => {
expect(resp).toBe(null);
});
}
});

it('should validate name (2/4)', () => {
const validatorFn = component.bucketNameValidator();
const ctrl = new FormControl('ab');
ctrl.markAsDirty();
const validatorPromise = validatorFn(ctrl);
expect(validatorPromise instanceof Promise).toBeTruthy();
if (validatorPromise instanceof Promise) {
validatorPromise.then((resp) => {
expect(resp.bucketNameInvalid).toBeTruthy();
});
}
});

it('should validate name (3/4)', () => {
const validatorFn = component.bucketNameValidator();
const ctrl = new FormControl('abc');
ctrl.markAsDirty();
const validatorPromise = validatorFn(ctrl);
expect(validatorPromise instanceof Promise).toBeTruthy();
if (validatorPromise instanceof Promise) {
validatorPromise.then((resp) => {
expect(resp).toBe(null);
});
}
});

it('should validate name (4/4)', () => {
queryResult = ['abcd'];
const validatorFn = component.bucketNameValidator();
const ctrl = new FormControl('abcd');
ctrl.markAsDirty();
const validatorPromise = validatorFn(ctrl);
expect(validatorPromise instanceof Promise).toBeTruthy();
if (validatorPromise instanceof Promise) {
validatorPromise.then((resp) => {
expect(resp.bucketNameExists).toBeTruthy();
});
}
});
});
});

0 comments on commit d75bc5d

Please sign in to comment.