from .. import mgr
from ..security import Permission, Scope
from ..services.ceph_service import CephService
-from ..services.tcmu_service import TcmuService
+from ..services.iscsi_cli import IscsiGatewaysConfig
class HealthData(object):
return len(mgr.list_servers())
def iscsi_daemons(self):
- return TcmuService.get_iscsi_daemons_amount()
+ gateways = IscsiGatewaysConfig.get_gateways_config()['gateways']
+ return len(gateways) if gateways else 0
def mgr_map(self):
mgr_map = mgr.get('mgr_map')
from ..services.iscsi_client import IscsiClient
from ..services.iscsi_cli import IscsiGatewaysConfig
from ..services.rbd import format_bitmask
+from ..services.tcmu_service import TcmuService
from ..exceptions import DashboardException
from ..tools import TaskManager
portals.append({'name': name, 'ip_addresses': ip_addresses['data']})
return sorted(portals, key=lambda p: '{}.{}'.format(p['name'], p['ip_addresses']))
+ @Endpoint()
+ @ReadPermission
+ def overview(self):
+ result_gateways = []
+ result_images = []
+ gateways_names = IscsiGatewaysConfig.get_gateways_config()['gateways'].keys()
+ config = None
+ for gateway_name in gateways_names:
+ try:
+ config = IscsiClient.instance(gateway_name=gateway_name).get_config()
+ break
+ except RequestException:
+ pass
+
+ # Gateways info
+ for gateway_name in gateways_names:
+ gateway = {
+ 'name': gateway_name,
+ 'state': 'N/A',
+ 'num_targets': 'N/A'
+ }
+ try:
+ IscsiClient.instance(gateway_name=gateway_name).ping()
+ gateway['state'] = 'up'
+ except RequestException:
+ gateway['state'] = 'down'
+ if config:
+ gateway['num_targets'] = len([target for _, target in config['targets'].items()
+ if gateway_name in target['portals']])
+ result_gateways.append(gateway)
+
+ # Images info
+ if config:
+ tcmu_info = TcmuService.get_iscsi_info()
+ for _, disk_config in config['disks'].items():
+ image = {
+ 'pool': disk_config['pool'],
+ 'image': disk_config['image'],
+ 'backstore': disk_config['backstore'],
+ 'optimized_since': None,
+ 'stats': None,
+ 'stats_history': None
+ }
+ tcmu_image_info = TcmuService.get_image_info(image['pool'],
+ image['image'],
+ tcmu_info)
+ if tcmu_image_info:
+ if 'optimized_since' in tcmu_image_info:
+ image['optimized_since'] = tcmu_image_info['optimized_since']
+ if 'stats' in tcmu_image_info:
+ image['stats'] = tcmu_image_info['stats']
+ if 'stats_history' in tcmu_image_info:
+ image['stats_history'] = tcmu_image_info['stats_history']
+ result_images.append(image)
+
+ return {
+ 'gateways': sorted(result_gateways, key=lambda g: g['name']),
+ 'images': sorted(result_images, key=lambda i: '{}/{}'.format(i['pool'], i['image']))
+ }
+
@ApiController('/iscsi', Scope.ISCSI)
class Iscsi(BaseController):
+++ /dev/null
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-
-from . import ApiController, RESTController
-from ..security import Scope
-from ..services.tcmu_service import TcmuService
-
-
-@ApiController('/tcmuiscsi', Scope.ISCSI)
-class TcmuIscsi(RESTController):
-
- def list(self):
- return TcmuService.get_iscsi_info()
<cd-iscsi-tabs></cd-iscsi-tabs>
-<legend i18n>Daemons</legend>
-<cd-table [data]="daemons"
+<legend i18n>Gateways</legend>
+<cd-table [data]="gateways"
(fetchData)="refresh()"
- [columns]="daemonsColumns">
+ [columns]="gatewaysColumns">
</cd-table>
<legend i18n>Images</legend>
<cd-table [data]="images"
[columns]="imagesColumns">
</cd-table>
+
+<ng-template #statusColorTpl
+ let-value="value">
+ <span class="label"
+ [ngClass]="{'label-success': 'up' == value, 'label-danger': 'down' == value}">{{ value }}</span>
+</ng-template>
+
+<ng-template #iscsiSparklineTpl
+ let-row="row"
+ let-value="value">
+ <span *ngIf="row.backstore === 'user:rbd'">
+ <cd-sparkline [data]="value"
+ [isBinary]="row.cdIsBinary"></cd-sparkline>
+ </span>
+ <span *ngIf="row.backstore !== 'user:rbd'"
+ class="text-muted">
+ n/a
+ </span>
+</ng-template>
+
+<ng-template #iscsiPerSecondTpl
+ let-row="row"
+ let-value="value">
+ <span *ngIf="row.backstore === 'user:rbd'">
+ {{ value }} /s
+ </span>
+ <span *ngIf="row.backstore !== 'user:rbd'"
+ class="text-muted">
+ n/a
+ </span>
+</ng-template>
+
+<ng-template #iscsiRelativeDateTpl
+ let-row="row"
+ let-value="value">
+ <span *ngIf="row.backstore === 'user:rbd'">
+ {{ value | relativeDate }}
+ </span>
+ <span *ngIf="row.backstore !== 'user:rbd'"
+ class="text-muted">
+ n/a
+ </span>
+</ng-template>
import { of } from 'rxjs';
import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
-import { TcmuIscsiService } from '../../../shared/api/tcmu-iscsi.service';
+import { IscsiService } from '../../../shared/api/iscsi.service';
import { CephShortVersionPipe } from '../../../shared/pipes/ceph-short-version.pipe';
import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
import { ListPipe } from '../../../shared/pipes/list.pipe';
describe('IscsiComponent', () => {
let component: IscsiComponent;
let fixture: ComponentFixture<IscsiComponent>;
- let tcmuIscsiService: TcmuIscsiService;
+ let iscsiService: IscsiService;
let tcmuiscsiData;
const fakeService = {
- tcmuiscsi: () => {
+ overview: () => {
return new Promise(function() {
return;
});
FormatterService,
RelativeDatePipe,
ListPipe,
- { provide: TcmuIscsiService, useValue: fakeService },
+ { provide: IscsiService, useValue: fakeService },
i18nProviders
]
});
beforeEach(() => {
fixture = TestBed.createComponent(IscsiComponent);
component = fixture.componentInstance;
- tcmuIscsiService = TestBed.get(TcmuIscsiService);
+ iscsiService = TestBed.get(IscsiService);
fixture.detectChanges();
tcmuiscsiData = {
images: []
};
- spyOn(tcmuIscsiService, 'tcmuiscsi').and.callFake(() => of(tcmuiscsiData));
+ spyOn(iscsiService, 'overview').and.callFake(() => of(tcmuiscsiData));
});
it('should create', () => {
-import { Component } from '@angular/core';
+import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { I18n } from '@ngx-translate/i18n-polyfill';
-import { TcmuIscsiService } from '../../../shared/api/tcmu-iscsi.service';
-import { CellTemplate } from '../../../shared/enum/cell-template.enum';
-import { CephShortVersionPipe } from '../../../shared/pipes/ceph-short-version.pipe';
+import { IscsiService } from '../../../shared/api/iscsi.service';
import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
-import { ListPipe } from '../../../shared/pipes/list.pipe';
import { RelativeDatePipe } from '../../../shared/pipes/relative-date.pipe';
@Component({
templateUrl: './iscsi.component.html',
styleUrls: ['./iscsi.component.scss']
})
-export class IscsiComponent {
- daemons = [];
- daemonsColumns: any;
+export class IscsiComponent implements OnInit {
+ @ViewChild('statusColorTpl')
+ statusColorTpl: TemplateRef<any>;
+ @ViewChild('iscsiSparklineTpl')
+ iscsiSparklineTpl: TemplateRef<any>;
+ @ViewChild('iscsiPerSecondTpl')
+ iscsiPerSecondTpl: TemplateRef<any>;
+ @ViewChild('iscsiRelativeDateTpl')
+ iscsiRelativeDateTpl: TemplateRef<any>;
+
+ gateways = [];
+ gatewaysColumns: any;
images = [];
imagesColumns: any;
constructor(
- private tcmuIscsiService: TcmuIscsiService,
- cephShortVersionPipe: CephShortVersionPipe,
- dimlessPipe: DimlessPipe,
- relativeDatePipe: RelativeDatePipe,
- listPipe: ListPipe,
+ private iscsiService: IscsiService,
+ private dimlessPipe: DimlessPipe,
+ private relativeDatePipe: RelativeDatePipe,
private i18n: I18n
- ) {
- this.daemonsColumns = [
- {
- name: this.i18n('Hostname'),
- prop: 'server_hostname'
- },
+ ) {}
+
+ ngOnInit() {
+ this.gatewaysColumns = [
{
- name: this.i18n('# Active/Optimized'),
- prop: 'optimized_paths'
+ name: this.i18n('Name'),
+ prop: 'name'
},
{
- name: this.i18n('# Active/Non-Optimized'),
- prop: 'non_optimized_paths'
+ name: this.i18n('State'),
+ prop: 'state',
+ cellTemplate: this.statusColorTpl
},
{
- name: this.i18n('Version'),
- prop: 'version',
- pipe: cephShortVersionPipe
+ name: this.i18n('# Targets'),
+ prop: 'num_targets'
}
];
this.imagesColumns = [
{
name: this.i18n('Pool'),
- prop: 'pool_name'
+ prop: 'pool'
},
{
name: this.i18n('Image'),
- prop: 'name'
- },
- {
- name: this.i18n('Active/Optimized'),
- prop: 'optimized_paths',
- pipe: listPipe
+ prop: 'image'
},
{
- name: this.i18n('Active/Non-Optimized'),
- prop: 'non_optimized_paths',
- pipe: listPipe
+ name: this.i18n('Backstore'),
+ prop: 'backstore'
},
{
name: this.i18n('Read Bytes'),
prop: 'stats_history.rd_bytes',
- cellTransformation: CellTemplate.sparkline
+ cellTemplate: this.iscsiSparklineTpl
},
{
name: this.i18n('Write Bytes'),
prop: 'stats_history.wr_bytes',
- cellTransformation: CellTemplate.sparkline
+ cellTemplate: this.iscsiSparklineTpl
},
{
name: this.i18n('Read Ops'),
prop: 'stats.rd',
- pipe: dimlessPipe,
- cellTransformation: CellTemplate.perSecond
+ pipe: this.dimlessPipe,
+ cellTemplate: this.iscsiPerSecondTpl
},
{
name: this.i18n('Write Ops'),
prop: 'stats.wr',
- pipe: dimlessPipe,
- cellTransformation: CellTemplate.perSecond
+ pipe: this.dimlessPipe,
+ cellTemplate: this.iscsiPerSecondTpl
},
{
name: this.i18n('A/O Since'),
prop: 'optimized_since',
- pipe: relativeDatePipe
+ cellTemplate: this.iscsiRelativeDateTpl
}
];
}
refresh() {
- this.tcmuIscsiService.tcmuiscsi().subscribe((resp: any) => {
- this.daemons = resp.daemons;
- this.images = resp.images;
+ this.iscsiService.overview().subscribe((overview: Array<any>) => {
+ this.gateways = overview['gateways'];
+ this.images = overview['images'];
this.images.map((image) => {
if (image.stats_history) {
image.stats_history.rd_bytes = image.stats_history.rd_bytes.map((i) => i[1]);
updateDiscovery(auth) {
return this.http.put(`api/iscsi/discoveryauth`, auth);
}
+
+ overview() {
+ return this.http.get(`ui-api/iscsi/overview`);
+ }
}
+++ /dev/null
-import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
-import { fakeAsync, TestBed, tick } from '@angular/core/testing';
-
-import { configureTestBed } from '../../../testing/unit-test-helper';
-import { TcmuIscsiService } from './tcmu-iscsi.service';
-
-describe('TcmuIscsiService', () => {
- let service: TcmuIscsiService;
- let httpTesting: HttpTestingController;
-
- configureTestBed({
- providers: [TcmuIscsiService],
- imports: [HttpClientTestingModule]
- });
-
- beforeEach(() => {
- service = TestBed.get(TcmuIscsiService);
- httpTesting = TestBed.get(HttpTestingController);
- });
-
- afterEach(() => {
- httpTesting.verify();
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-
- it('should call tcmuiscsi', fakeAsync(() => {
- let result;
- service.tcmuiscsi().subscribe((resp) => {
- result = resp;
- });
- const req = httpTesting.expectOne('api/tcmuiscsi');
- expect(req.request.method).toBe('GET');
- req.flush(['foo']);
- tick();
- expect(result).toEqual(['foo']);
- }));
-});
+++ /dev/null
-import { HttpClient } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-
-import { ApiModule } from './api.module';
-
-@Injectable({
- providedIn: ApiModule
-})
-export class TcmuIscsiService {
- constructor(private http: HttpClient) {}
-
- tcmuiscsi() {
- return this.http.get('api/tcmuiscsi');
- }
-}
<context context-type="sourcefile">app/ceph/block/mirroring/overview/overview.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">app/ceph/block/iscsi/iscsi.component.html</context>
- <context context-type="linenumber">3</context>
- </context-group>
</trans-unit><trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be" datatype="html">
<source>Users</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/block/rbd-trash-move-modal/rbd-trash-move-modal.component.html</context>
<context context-type="linenumber">40</context>
</context-group>
+ </trans-unit><trans-unit id="8a9910cd114c1deb9af74f6f99b4173403965bf2" datatype="html">
+ <source>Gateways</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/block/iscsi/iscsi.component.html</context>
+ <context context-type="linenumber">3</context>
+ </context-group>
</trans-unit><trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5" datatype="html">
<source>Overview</source>
<context-group purpose="location">
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
- <trans-unit id="9a541ec1a4319fffc16ad3b3ab2c2b6d251a829d" datatype="html">
- <source>Hostname</source>
+ <trans-unit id="873b72903b1858a9cd6c8967521030b4d7d1435b" datatype="html">
+ <source>State</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/ceph/block/iscsi/iscsi.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/block/mirroring/daemon-list/daemon-list.component.ts</context>
+ <context context-type="sourcefile">src/app/ceph/block/mirroring/image-list/image-list.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/cluster/hosts/hosts.component.ts</context>
+ <context context-type="sourcefile">src/app/ceph/block/mirroring/image-list/image-list.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts</context>
+ <context context-type="sourcefile">src/app/ceph/block/mirroring/image-list/image-list.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
- </trans-unit>
- <trans-unit id="32b73dd480452aed3c8df00055279a110b3e633c" datatype="html">
- <source># Active/Optimized</source>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/block/iscsi/iscsi.component.ts</context>
+ <context context-type="sourcefile">src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
- </trans-unit>
- <trans-unit id="26a3d69b872abf67663ba7473606ea32d6128531" datatype="html">
- <source># Active/Non-Optimized</source>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/block/iscsi/iscsi.component.ts</context>
+ <context context-type="sourcefile">src/app/ceph/cephfs/cephfs-detail/cephfs-detail.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
- </trans-unit>
- <trans-unit id="2cd86fc9c2dc1b4398514266a08e507140fe5ba8" datatype="html">
- <source>Active/Optimized</source>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/block/iscsi/iscsi.component.ts</context>
+ <context context-type="sourcefile">src/app/ceph/cluster/prometheus/prometheus-list/prometheus-list.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
- <trans-unit id="c74c467d73caf2b951bc1a1d52d6e93f9c5e795b" datatype="html">
- <source>Active/Non-Optimized</source>
+ <trans-unit id="02b75464c0dc0cf49ea0208211b6fbcad3bf728a" datatype="html">
+ <source># Targets</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/ceph/block/iscsi/iscsi.component.ts</context>
<context context-type="linenumber">1</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
- <trans-unit id="36fad7995cadfb3a6f0d43ef2db280146b3ca022" datatype="html">
- <source>Issue</source>
+ <trans-unit id="9a541ec1a4319fffc16ad3b3ab2c2b6d251a829d" datatype="html">
+ <source>Hostname</source>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/block/mirroring/image-list/image-list.component.ts</context>
+ <context context-type="sourcefile">src/app/ceph/block/mirroring/daemon-list/daemon-list.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
- </trans-unit>
- <trans-unit id="873b72903b1858a9cd6c8967521030b4d7d1435b" datatype="html">
- <source>State</source>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/block/mirroring/image-list/image-list.component.ts</context>
+ <context context-type="sourcefile">src/app/ceph/cluster/hosts/hosts.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
<context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/block/mirroring/image-list/image-list.component.ts</context>
+ <context context-type="sourcefile">src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
+ </trans-unit>
+ <trans-unit id="36fad7995cadfb3a6f0d43ef2db280146b3ca022" datatype="html">
+ <source>Issue</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/ceph/block/mirroring/image-list/image-list.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts</context>
- <context context-type="linenumber">1</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/cephfs/cephfs-detail/cephfs-detail.component.ts</context>
- <context context-type="linenumber">1</context>
- </context-group>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/ceph/cluster/prometheus/prometheus-list/prometheus-list.component.ts</context>
- <context context-type="linenumber">1</context>
- </context-group>
</trans-unit>
<trans-unit id="15793f4cbc261bedbc60f7105533dde536a3f42b" datatype="html">
<source>Progress</source>
}
@staticmethod
- def get_iscsi_daemons_amount():
- daemons = {}
- for service in CephService.get_service_list(SERVICE_TYPE):
- hostname = service['hostname']
-
- daemon = daemons.get(hostname, None)
- if daemon is None:
- daemons[hostname] = True
-
- return len(daemons)
+ def get_image_info(pool_name, image_name, get_iscsi_info):
+ for image in get_iscsi_info['images']:
+ if image['pool_name'] == pool_name and image['name'] == image_name:
+ return image
+ return None
+++ /dev/null
-from __future__ import absolute_import
-
-from . import ControllerTestCase
-from .. import mgr
-from ..controllers.tcmu_iscsi import TcmuIscsi
-
-mocked_servers = [{
- 'ceph_version': 'ceph version 13.0.0-5083- () mimic (dev)',
- 'hostname': 'ceph-dev1',
- 'services': [{'id': 'ceph-dev1:pool1/image1', 'type': 'tcmu-runner'}]
-}, {
- 'ceph_version': 'ceph version 13.0.0-5083- () mimic (dev)',
- 'hostname': 'ceph-dev2',
- 'services': [{'id': 'ceph-dev2:pool1/image1', 'type': 'tcmu-runner'}]
-}]
-
-mocked_metadata1 = {
- 'ceph_version': 'ceph version 13.0.0-5083- () mimic (dev)',
- 'pool_name': 'pool1',
- 'image_name': 'image1',
- 'image_id': '42',
- 'optimized_since': 1152121348,
-}
-
-mocked_metadata2 = {
- 'ceph_version': 'ceph version 13.0.0-5083- () mimic (dev)',
- 'pool_name': 'pool1',
- 'image_name': 'image1',
- 'image_id': '42',
- 'optimized_since': 0,
-}
-
-mocked_get_daemon_status = {
- 'lock_owner': 'true',
-}
-
-mocked_get_counter1 = {
- 'librbd-42-pool1-image1.lock_acquired_time': [[1152121348 * 1000000000,
- 1152121348 * 1000000000]],
- 'librbd-42-pool1-image1.rd': [[0, 0], [1, 43]],
- 'librbd-42-pool1-image1.wr': [[0, 0], [1, 44]],
- 'librbd-42-pool1-image1.rd_bytes': [[0, 0], [1, 45]],
- 'librbd-42-pool1-image1.wr_bytes': [[0, 0], [1, 46]],
-}
-
-mocked_get_counter2 = {
- 'librbd-42-pool1-image1.lock_acquired_time': [[0, 0]],
- 'librbd-42-pool1-image1.rd': [],
- 'librbd-42-pool1-image1.wr': [],
- 'librbd-42-pool1-image1.rd_bytes': [],
- 'librbd-42-pool1-image1.wr_bytes': [],
-}
-
-
-def _get_counter(_daemon_type, daemon_name, _stat):
- if daemon_name == 'ceph-dev1:pool1/image1':
- return mocked_get_counter1
- if daemon_name == 'ceph-dev2:pool1/image1':
- return mocked_get_counter2
- return Exception('invalid daemon name')
-
-
-class TcmuIscsiControllerTest(ControllerTestCase):
-
- @classmethod
- def setup_server(cls):
- mgr.list_servers.return_value = mocked_servers
- mgr.get_metadata.side_effect = [mocked_metadata1, mocked_metadata2]
- mgr.get_daemon_status.return_value = mocked_get_daemon_status
- mgr.get_counter.side_effect = _get_counter
- mgr.url_prefix = ''
- TcmuIscsi._cp_config['tools.authenticate.on'] = False # pylint: disable=protected-access
-
- cls.setup_controllers(TcmuIscsi, "/test")
-
- def test_list(self):
- self._get('/test/api/tcmuiscsi')
- self.assertStatus(200)
- self.assertJsonBody({
- 'daemons': [
- {
- 'server_hostname': 'ceph-dev1',
- 'version': 'ceph version 13.0.0-5083- () mimic (dev)',
- 'optimized_paths': 1, 'non_optimized_paths': 0},
- {
- 'server_hostname': 'ceph-dev2',
- 'version': 'ceph version 13.0.0-5083- () mimic (dev)',
- 'optimized_paths': 0, 'non_optimized_paths': 1}],
- 'images': [{
- 'device_id': 'pool1/image1',
- 'pool_name': 'pool1',
- 'name': 'image1',
- 'id': '42',
- 'optimized_paths': ['ceph-dev1'],
- 'non_optimized_paths': ['ceph-dev2'],
- 'optimized_daemon': 'ceph-dev1',
- 'optimized_since': 1152121348,
- 'stats': {
- 'rd': 43.0,
- 'wr': 44.0,
- 'rd_bytes': 45.0,
- 'wr_bytes': 46.0},
- 'stats_history': {
- 'rd': [[1, 43]],
- 'wr': [[1, 44]],
- 'rd_bytes': [[1, 45]],
- 'wr_bytes': [[1, 46]]}
- }]
- })