From 83441af93cae11e62286a99e65e470a5788e8429 Mon Sep 17 00:00:00 2001 From: Volker Theile Date: Mon, 7 May 2018 16:50:14 +0200 Subject: [PATCH] mgr/dashboard: Display useful info if RGW is not configured Signed-off-by: Volker Theile --- qa/tasks/mgr/dashboard/test_rgw.py | 17 ++++ src/pybind/mgr/dashboard/controllers/rgw.py | 22 ++++- .../frontend/src/app/app-routing.module.ts | 81 +++++++++++-------- .../ceph/rgw/rgw-501/rgw-501.component.html | 12 +++ .../ceph/rgw/rgw-501/rgw-501.component.scss | 6 ++ .../rgw/rgw-501/rgw-501.component.spec.ts | 31 +++++++ .../app/ceph/rgw/rgw-501/rgw-501.component.ts | 26 ++++++ .../frontend/src/app/ceph/rgw/rgw.module.ts | 12 ++- 8 files changed, 172 insertions(+), 35 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.ts diff --git a/qa/tasks/mgr/dashboard/test_rgw.py b/qa/tasks/mgr/dashboard/test_rgw.py index 81eced2f6d0..0c73b142a4c 100644 --- a/qa/tasks/mgr/dashboard/test_rgw.py +++ b/qa/tasks/mgr/dashboard/test_rgw.py @@ -31,6 +31,23 @@ class RgwControllerTest(DashboardTestCase): self.assertIn('rgw_status', data) self.assertTrue(data['rgw_metadata']) + @authenticate + def test_rgw_status(self): + self._radosgw_admin_cmd([ + 'user', 'create', '--uid=admin', '--display-name=admin', + '--system', '--access-key=admin', '--secret=admin' + ]) + self._ceph_cmd(['dashboard', 'set-rgw-api-user-id', 'admin']) + self._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', 'admin']) + self._ceph_cmd(['dashboard', 'set-rgw-api-access-key', 'admin']) + + data = self._get('/api/rgw/status') + self.assertStatus(200) + self.assertIn('available', data) + self.assertIn('message', data) + self.assertTrue(data['available']) + + class RgwProxyExceptionsTest(DashboardTestCase): @classmethod diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index ae0de6305b1..13f177a6069 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -14,7 +14,27 @@ from ..rest_client import RequestException @ApiController('rgw') @AuthRequired() class Rgw(RESTController): - pass + + @cherrypy.expose + @cherrypy.tools.json_out() + def status(self): + status = {'available': False, 'message': None} + try: + instance = RgwClient.admin_instance() + # Check if the service is online. + if not instance.is_service_online(): + status['message'] = 'Failed to connect to the Object Gateway\'s Admin Ops API.' + raise RequestException(status['message']) + # If the API user ID is configured via 'ceph dashboard set-rgw-api-user-id ' + # (which is not mandatory), then ensure it is known by the RGW. + if instance.userid and not instance.is_system_user(): + status['message'] = 'The user "{}" is unknown to the Object Gateway.'.format( + instance.userid) + raise RequestException(status['message']) + status['available'] = True + except RequestException: + pass + return status @ApiController('rgw/daemon') diff --git a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts index a4fb77be41f..bca500fff68 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts @@ -16,6 +16,7 @@ import { PerformanceCounterComponent } from './ceph/performance-counter/performance-counter/performance-counter.component'; import { PoolListComponent } from './ceph/pool/pool-list/pool-list.component'; +import { Rgw501Component } from './ceph/rgw/rgw-501/rgw-501.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'; @@ -24,6 +25,7 @@ import { RgwUserListComponent } from './ceph/rgw/rgw-user-list/rgw-user-list.com import { LoginComponent } from './core/auth/login/login.component'; import { NotFoundComponent } from './core/not-found/not-found.component'; import { AuthGuardService } from './shared/services/auth-guard.service'; +import { ModuleStatusGuardService } from './shared/services/module-status-guard.service'; const routes: Routes = [ { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, @@ -31,40 +33,53 @@ const routes: Routes = [ { path: 'hosts', component: HostsComponent, canActivate: [AuthGuardService] }, { path: 'login', component: LoginComponent }, { path: 'hosts', component: HostsComponent, canActivate: [AuthGuardService] }, + { path: 'rgw/501/:message', component: Rgw501Component, canActivate: [AuthGuardService] }, { - path: 'rgw/daemon', - component: RgwDaemonListComponent, - canActivate: [AuthGuardService] - }, - { - path: 'rgw/user', - 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: 'rgw', + canActivateChild: [ModuleStatusGuardService], + data: { + moduleStatusGuardConfig: { + apiPath: 'rgw', + redirectTo: 'rgw/501' + } + }, + children: [ + { + path: 'daemon', + component: RgwDaemonListComponent, + canActivate: [AuthGuardService] + }, + { + path: 'user', + component: RgwUserListComponent, + canActivate: [AuthGuardService] + }, + { + path: 'user/add', + component: RgwUserFormComponent, + canActivate: [AuthGuardService] + }, + { + path: 'user/edit/:uid', + component: RgwUserFormComponent, + canActivate: [AuthGuardService] + }, + { + path: 'bucket', + component: RgwBucketListComponent, + canActivate: [AuthGuardService] + }, + { + path: 'bucket/add', + component: RgwBucketFormComponent, + canActivate: [AuthGuardService] + }, + { + path: 'bucket/edit/:bucket', + component: RgwBucketFormComponent, + canActivate: [AuthGuardService] + } + ] }, { path: 'block/iscsi', component: IscsiComponent, canActivate: [AuthGuardService] }, { path: 'block/rbd', component: RbdListComponent, canActivate: [AuthGuardService] }, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.html new file mode 100644 index 00000000000..da92b5d7a3f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.html @@ -0,0 +1,12 @@ + + + {{ message }} + + Please consult the documentation on how to configure and enable the Object Gateway management functionality. + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.scss new file mode 100644 index 00000000000..f2cd3879364 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.scss @@ -0,0 +1,6 @@ +.alert-row-icon { + vertical-align: top; + padding-right: 15px; +} +.alert-row-text { +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.spec.ts new file mode 100644 index 00000000000..03368345504 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.spec.ts @@ -0,0 +1,31 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { SharedModule } from '../../../shared/shared.module'; +import { Rgw501Component } from './rgw-501.component'; + +describe('Rgw501Component', () => { + let component: Rgw501Component; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ Rgw501Component ], + imports: [ + RouterTestingModule, + SharedModule + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(Rgw501Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.ts new file mode 100644 index 00000000000..d25ac7ba697 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-501/rgw-501.component.ts @@ -0,0 +1,26 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import * as _ from 'lodash'; + +@Component({ + selector: 'cd-rgw-501', + templateUrl: './rgw-501.component.html', + styleUrls: ['./rgw-501.component.scss'] +}) +export class Rgw501Component implements OnInit, OnDestroy { + message = 'The Object Gateway service is not configured.'; + routeParamsSubscribe: any; + + constructor(private route: ActivatedRoute) {} + + ngOnInit() { + this.routeParamsSubscribe = this.route.params.subscribe((params: { message: string }) => { + this.message = params.message; + }); + } + + ngOnDestroy() { + this.routeParamsSubscribe.unsubscribe(); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts index 90623dc9868..8f82285bfad 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts @@ -2,11 +2,18 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { BsDropdownModule, ModalModule, TabsModule, TooltipModule } from 'ngx-bootstrap'; +import { + AlertModule, + BsDropdownModule, + ModalModule, + TabsModule, + TooltipModule +} from 'ngx-bootstrap'; import { AppRoutingModule } from '../../app-routing.module'; import { SharedModule } from '../../shared/shared.module'; import { PerformanceCounterModule } from '../performance-counter/performance-counter.module'; +import { Rgw501Component } from './rgw-501/rgw-501.component'; import { RgwBucketDetailsComponent } from './rgw-bucket-details/rgw-bucket-details.component'; import { RgwBucketFormComponent } from './rgw-bucket-form/rgw-bucket-form.component'; import { RgwBucketListComponent } from './rgw-bucket-list/rgw-bucket-list.component'; @@ -45,12 +52,14 @@ import { FormsModule, ReactiveFormsModule, PerformanceCounterModule, + AlertModule.forRoot(), BsDropdownModule.forRoot(), TabsModule.forRoot(), TooltipModule.forRoot(), ModalModule.forRoot() ], exports: [ + Rgw501Component, RgwDaemonListComponent, RgwDaemonDetailsComponent, RgwBucketFormComponent, @@ -60,6 +69,7 @@ import { RgwUserDetailsComponent ], declarations: [ + Rgw501Component, RgwDaemonListComponent, RgwDaemonDetailsComponent, RgwBucketFormComponent,