Skip to content

Commit

Permalink
mgr/dashboard: support creating OSDs on spare devices (ceph#30921)
Browse files Browse the repository at this point in the history
mgr/dashboard: support creating OSDs on spare devices

Reviewed-by: Tiago Melo <[email protected]>
Reviewed-by: Volker Theile <[email protected]>
Reviewed-by: Laura Paduano <[email protected]>
  • Loading branch information
votdev authored Nov 28, 2019
2 parents 041a71b + 3677ac0 commit 0cde6a0
Show file tree
Hide file tree
Showing 51 changed files with 2,018 additions and 192 deletions.
14 changes: 14 additions & 0 deletions src/pybind/mgr/dashboard/controllers/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from __future__ import absolute_import

import cherrypy
from ceph.deployment.drive_group import DriveGroupSpec, DriveGroupValidationError

from . import ApiController, Endpoint, ReadPermission
from . import RESTController, Task
from .. import mgr
from ..exceptions import DashboardException
from ..security import Scope
from ..services.orchestrator import OrchClient
from ..tools import wraps
Expand Down Expand Up @@ -94,3 +96,15 @@ class OrchestratorService(RESTController):
def list(self, hostname=None):
orch = OrchClient.instance()
return [service.to_json() for service in orch.services.list(None, None, hostname)]


@ApiController('/orchestrator/osd', Scope.OSD)
class OrchestratorOsd(RESTController):

@raise_if_no_orchestrator
def create(self, drive_group, all_hosts=None):
orch = OrchClient.instance()
try:
orch.osds.create(DriveGroupSpec.from_json(drive_group), all_hosts)
except (ValueError, TypeError, DriveGroupValidationError) as e:
raise DashboardException(e, component='osd')
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('OSDs page', () => {
});

it('should verify that buttons exist', async () => {
await expect(element(by.cssContainingText('button', 'Scrub')).isPresent()).toBe(true);
await expect(element(by.cssContainingText('button', 'Create')).isPresent()).toBe(true);
await expect(
element(by.cssContainingText('button', 'Cluster-wide configuration')).isPresent()
).toBe(true);
Expand Down
10 changes: 9 additions & 1 deletion src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { LogsComponent } from './ceph/cluster/logs/logs.component';
import { MgrModuleFormComponent } from './ceph/cluster/mgr-modules/mgr-module-form/mgr-module-form.component';
import { MgrModuleListComponent } from './ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component';
import { MonitorComponent } from './ceph/cluster/monitor/monitor.component';
import { OsdFormComponent } from './ceph/cluster/osd/osd-form/osd-form.component';
import { OsdListComponent } from './ceph/cluster/osd/osd-list/osd-list.component';
import { AlertListComponent } from './ceph/cluster/prometheus/alert-list/alert-list.component';
import { SilenceFormComponent } from './ceph/cluster/prometheus/silence-form/silence-form.component';
Expand Down Expand Up @@ -107,7 +108,14 @@ const routes: Routes = [
canActivate: [AuthGuardService],
canActivateChild: [AuthGuardService],
data: { breadcrumbs: 'Cluster/OSDs' },
children: [{ path: '', component: OsdListComponent }]
children: [
{ path: '', component: OsdListComponent },
{
path: URLVerbs.CREATE,
component: OsdFormComponent,
data: { breadcrumbs: ActionLabels.CREATE }
}
]
},
{
path: 'configuration',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ import { CrushmapComponent } from './crushmap/crushmap.component';
import { HostDetailsComponent } from './hosts/host-details/host-details.component';
import { HostFormComponent } from './hosts/host-form/host-form.component';
import { HostsComponent } from './hosts/hosts.component';
import { InventoryDevicesComponent } from './inventory/inventory-devices/inventory-devices.component';
import { InventoryComponent } from './inventory/inventory.component';
import { LogsComponent } from './logs/logs.component';
import { MgrModulesModule } from './mgr-modules/mgr-modules.module';
import { MonitorComponent } from './monitor/monitor.component';
import { OsdCreationPreviewModalComponent } from './osd/osd-creation-preview-modal/osd-creation-preview-modal.component';
import { OsdDetailsComponent } from './osd/osd-details/osd-details.component';
import { OsdDevicesSelectionGroupsComponent } from './osd/osd-devices-selection-groups/osd-devices-selection-groups.component';
import { OsdDevicesSelectionModalComponent } from './osd/osd-devices-selection-modal/osd-devices-selection-modal.component';
import { OsdFlagsModalComponent } from './osd/osd-flags-modal/osd-flags-modal.component';
import { OsdFormComponent } from './osd/osd-form/osd-form.component';
import { OsdListComponent } from './osd/osd-list/osd-list.component';
import { OsdPerformanceHistogramComponent } from './osd/osd-performance-histogram/osd-performance-histogram.component';
import { OsdPgScrubModalComponent } from './osd/osd-pg-scrub-modal/osd-pg-scrub-modal.component';
Expand All @@ -53,7 +58,9 @@ import { ServicesComponent } from './services/services.component';
OsdReweightModalComponent,
OsdPgScrubModalComponent,
OsdReweightModalComponent,
SilenceMatcherModalComponent
SilenceMatcherModalComponent,
OsdDevicesSelectionModalComponent,
OsdCreationPreviewModalComponent
],
imports: [
CommonModule,
Expand Down Expand Up @@ -102,7 +109,12 @@ import { ServicesComponent } from './services/services.component';
ServicesComponent,
InventoryComponent,
HostFormComponent,
OsdSmartListComponent
OsdSmartListComponent,
OsdFormComponent,
OsdDevicesSelectionModalComponent,
InventoryDevicesComponent,
OsdDevicesSelectionGroupsComponent,
OsdCreationPreviewModalComponent
]
})
export class ClusterModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
<tab i18n-heading
heading="Services"
*ngIf="permissions.hosts.read">
<cd-services [hostname]="selection.first()['hostname']">
<cd-services
[hostname]="selection.first()['hostname']"
[hiddenColumns]="['nodename']">
</cd-services>
</tab>
<tab i18n-heading
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { RouterTestingModule } from '@angular/router/testing';

import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { TabsetComponent, TabsModule } from 'ngx-bootstrap/tabs';

import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
import { CoreModule } from '../../../../core/core.module';
import { OrchestratorService } from '../../../../shared/api/orchestrator.service';
Expand All @@ -23,6 +23,7 @@ describe('HostDetailsComponent', () => {
HttpClientTestingModule,
TabsModule.forRoot(),
BsDropdownModule.forRoot(),
NgBootstrapFormValidationModule.forRoot(),
RouterTestingModule,
CephModule,
CoreModule
Expand All @@ -41,7 +42,7 @@ describe('HostDetailsComponent', () => {
});
const orchService = TestBed.get(OrchestratorService);
spyOn(orchService, 'status').and.returnValue(of({ available: true }));
spyOn(orchService, 'inventoryList').and.returnValue(of([]));
spyOn(orchService, 'inventoryDeviceList').and.returnValue(of([]));
spyOn(orchService, 'serviceList').and.returnValue(of([]));
fixture.detectChanges();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { RouterTestingModule } from '@angular/router/testing';

import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { TabsModule } from 'ngx-bootstrap/tabs';

import { ToastrModule } from 'ngx-toastr';
import { of } from 'rxjs';

import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
import { CoreModule } from '../../../core/core.module';
import { HostService } from '../../../shared/api/host.service';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface InventoryDeviceAppliedFilter {
label: string;
prop: string;
value: string;
formatValue: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { PipeTransform } from '@angular/core';

export interface InventoryDeviceFilter {
label: string;
prop: string;
initValue: string;
value: string;
options: {
value: string;
formatValue: string;
}[];
pipe?: PipeTransform;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { InventoryDeviceAppliedFilter } from './inventory-device-applied-filters.interface';
import { InventoryDevice } from './inventory-device.model';

export interface InventoryDeviceFiltersChangeEvent {
filters: InventoryDeviceAppliedFilter[];
filterInDevices: InventoryDevice[];
filterOutDevices: InventoryDevice[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,15 @@ export class SysAPI {
human_readable_size: string;
}

export class Device {
export class InventoryDevice {
hostname: string;
uid: string;
osd_ids: number[];

path: string;
sys_api: SysAPI;
available: boolean;
rejected_reasons: string[];
device_id: string;
human_readable_type: string;
}

export class InventoryNode {
name: string;
devices: Device[];
osd_ids: number[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<cd-table [data]="filterInDevices"
[columns]="columns"
identifier="uid"
[forceIdentifier]="true"
[selectionType]="selectionType"
columnMode="flex"
[autoReload]="false"
[searchField]="false">
<div class="table-filters form-inline"
*ngIf="filters.length !== 0">
<div class="form-group filter tc_filter"
*ngFor="let filter of filters">
<label class="col-form-label"><span>{{ filter.label }}</span><span>: </span></label>
<select class="custom-select"
[(ngModel)]="filter.value"
[ngModelOptions]="{standalone: true}"
(ngModelChange)="onFilterChange()"
[disabled]="filter.disabled">
<option *ngFor="let opt of filter.options"
[value]="opt.value">{{ opt.formatValue }}</option>
</select>
</div>
<div class="widget-toolbar tc_refreshBtn"
*ngIf="filters.length !== 0">
<button type="button"
title="Reset filters"
class="btn btn-light"
(click)="onFilterReset()">
<span [ngClass]="[icons.stack]">
<i [ngClass]="[icons.filter, icons.stack2x]"></i>
<i [ngClass]="[icons.destroy, icons.stack1x]"></i>
</span>
</button>
</div>
</div>
</cd-table>

<ng-template #osds
let-value="value">
<span *ngFor="let osdId of value; last as last">
<span class="badge badge-dark">osd.{{ osdId }}</span>
<span *ngIf="!last">&nbsp;</span>
</span>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.filter {
padding-right: 8px;
}

.fa-stack {
font-size: 0.79rem;

.fa-stack-1x {
margin-left: 8px;
margin-top: 5px;
}
}
Loading

0 comments on commit 0cde6a0

Please sign in to comment.