"used_profiles": used_profiles,
'nodes': mgr.get('osd_map_tree')['nodes']
}
+
+
+class RBDPool(Pool):
+ def create(self, pool='rbd-mirror'): # pylint: disable=arguments-differ
+ super().create(pool, pg_num=1, pool_type='replicated',
+ rule_name='replicated_rule', application_metadata=['rbd'])
import rbd
from .. import mgr
+from ..controllers.pool import RBDPool
+from ..controllers.service import Service
from ..security import Scope
from ..services.ceph_service import CephService
from ..services.exception import handle_rados_error, handle_rbd_error, serialize_dashboard_exception
+from ..services.orchestrator import OrchClient
from ..services.rbd import rbd_call
from ..tools import ViewCache
-from . import APIDoc, APIRouter, BaseController, Endpoint, EndpointDoc, \
- ReadPermission, RESTController, Task, UpdatePermission, allow_empty_body
+from . import APIDoc, APIRouter, BaseController, CreatePermission, Endpoint, \
+ EndpointDoc, ReadPermission, RESTController, Task, UIRouter, \
+ UpdatePermission, allow_empty_body
try:
from typing import no_type_check
rbd.RBD().mirror_peer_set_attributes(ioctx, peer_uuid, attributes)
_reset_view_cache()
+
+
+@UIRouter('/block/mirroring', Scope.RBD_MIRRORING)
+class RbdMirroringStatus(BaseController):
+ @EndpointDoc('Display RBD Mirroring Status')
+ @Endpoint()
+ @ReadPermission
+ def status(self):
+ status = {'available': True, 'message': None}
+ orch_status = OrchClient.instance().status()
+
+ # if the orch is not available we can't create the service
+ # using dashboard.
+ if not orch_status['available']:
+ return status
+ if not CephService.get_service_list('rbd-mirror') or not CephService.get_pool_list('rbd'):
+ status['available'] = False
+ status['message'] = 'RBD mirroring is not configured' # type: ignore
+ return status
+
+ @Endpoint('POST')
+ @EndpointDoc('Configure RBD Mirroring')
+ @CreatePermission
+ def configure(self):
+ rbd_pool = RBDPool()
+ service = Service()
+
+ service_spec = {
+ 'service_type': 'rbd-mirror',
+ 'placement': {},
+ 'unmanaged': False
+ }
+
+ if not CephService.get_service_list('rbd-mirror'):
+ service.create(service_spec, 'rbd-mirror')
+
+ if not CephService.get_pool_list('rbd'):
+ rbd_pool.create()
{
path: 'mirroring',
component: RbdMirroringComponent,
- canActivate: [FeatureTogglesGuardService],
- data: { breadcrumbs: 'Mirroring' },
+ canActivate: [FeatureTogglesGuardService, ModuleStatusGuardService],
+ data: {
+ moduleStatusGuardConfig: {
+ uiApiPath: 'block/mirroring',
+ redirectTo: 'error',
+ header: $localize`RBD mirroring is not configured`,
+ button_name: $localize`Configure RBD Mirroring`,
+ button_title: $localize`This will create rbd-mirror service and a replicated RBD pool`,
+ component: 'RBD Mirroring',
+ uiConfig: true
+ },
+ breadcrumbs: 'Mirroring'
+ },
children: [
{
path: `${URLVerbs.EDIT}/:pool_name`,
the {{ section_info }} management functionality.</h4>
</div>
<br><br>
+ <div *ngIf="(button_name && button_route) || bootstrap; else dashboardButton">
+ <button class="btn btn-primary"
+ [routerLink]="button_route"
+ *ngIf="button_name && button_route && !bootstrap"
+ i18n>{{ button_name }}</button>
+
+ <button class="btn btn-primary"
+ (click)="doBootstrap()"
+ *ngIf="bootstrap"
+ i18n>{{ button_name }}</button>
+ </div>
+ <ng-template #dashboardButton>
<div>
<button class="btn btn-primary"
[routerLink]="'/dashboard'"
+import { HttpClient } from '@angular/common/http';
import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
import { NavigationEnd, Router, RouterEvent } from '@angular/router';
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { DocService } from '~/app/shared/services/doc.service';
+import { NotificationService } from '~/app/shared/services/notification.service';
@Component({
selector: 'cd-error',
docUrl: string;
source: string;
routerSubscription: Subscription;
+ bootstrap: string;
+ uiApiPath: string;
+ button_route: string;
+ button_name: string;
- constructor(private router: Router, private docService: DocService) {}
+ constructor(private router: Router, private docService: DocService,
+ private http: HttpClient,
+ private notificationService: NotificationService, ) {}
ngOnInit() {
this.fetchData();
});
}
+ doBootstrap() {
+ this.http.post(`ui-api/${this.uiApiPath}/configure`, {}).subscribe({
+ next: () => {
+ this.notificationService.show(
+ NotificationType.info,
+ 'Configuring RBD Mirroring'
+ );
+ },
+ error: (error: any) => {
+ this.notificationService.show(
+ NotificationType.error,
+ error
+ );
+ },
+ complete: () => {
+ setTimeout(() => {
+ this.router.navigate([this.uiApiPath]);
+ this.notificationService.show(
+ NotificationType.success,
+ 'Configured RBD Mirroring'
+ );
+ }, 3000);
+ }
+ });
+ }
+
@HostListener('window:beforeunload', ['$event']) unloadHandler(event: Event) {
event.returnValue = false;
}
this.section_info = history.state.section_info;
this.icon = history.state.icon;
this.source = history.state.source;
+ this.bootstrap = history.state.bootstrap;
+ this.uiApiPath = history.state.uiApiPath;
+ this.button_route = history.state.button_route;
+ this.button_name = history.state.button_name;
this.docUrl = this.docService.urlGenerator(this.section);
} catch (error) {
this.router.navigate(['/error']);
message: resp.message,
section: config.section,
section_info: config.section_info,
+ button_name: config.button_name,
+ button_route: config.button_route,
+ button_title: config.button_title,
+ uiConfig: config.uiConfig,
+ uiApiPath: config.uiApiPath,
icon: Icons.wrench
}
});
import unittest.mock as mock
from .. import mgr
-from ..controllers.rbd_mirroring import RbdMirroring, RbdMirroringPoolBootstrap, RbdMirroringSummary
+from ..controllers.orchestrator import Orchestrator
+from ..controllers.rbd_mirroring import RbdMirroring, \
+ RbdMirroringPoolBootstrap, RbdMirroringStatus, RbdMirroringSummary
from ..controllers.summary import Summary
from ..services import progress
from ..tests import ControllerTestCase
summary = self.json_body()['rbd_mirroring']
self.assertEqual(summary, {'errors': 0, 'warnings': 1})
+
+
+class RbdMirroringStatusControllerTest(ControllerTestCase):
+
+ @classmethod
+ def setup_server(cls):
+ cls.setup_controllers([RbdMirroringStatus, Orchestrator])
+
+ @mock.patch('dashboard.controllers.orchestrator.OrchClient.instance')
+ def test_status(self, instance):
+ status = {'available': False, 'description': ''}
+ fake_client = mock.Mock()
+ fake_client.status.return_value = status
+ instance.return_value = fake_client
+
+ self._get('/ui-api/block/mirroring/status')
+ self.assertStatus(200)
+ self.assertJsonBody({'available': True, 'message': None})
+
+ def test_configure(self):
+ self._post('/ui-api/block/mirroring/configure')
+ self.assertStatus(200)