(import 'dashboards/rgw.libsonnet') +
(import 'dashboards/ceph-cluster.libsonnet') +
(import 'dashboards/rgw-s3-analytics.libsonnet') +
+ (import 'dashboards/multi-cluster.libsonnet') +
{ _config:: $._config },
}
--- /dev/null
+local g = import 'grafonnet/grafana.libsonnet';
+
+(import 'utils.libsonnet') {
+ 'multi-cluster-overview.json':
+ $.dashboardSchema(
+ 'Ceph - Multi-cluster',
+ '',
+ 'BnxelG7Sz',
+ 'now-1h',
+ '30s',
+ 22,
+ $._config.dashboardTags,
+ ''
+ )
+ .addAnnotation(
+ $.addAnnotationSchema(
+ 1,
+ '-- Grafana --',
+ true,
+ true,
+ 'rgba(0, 211, 255, 1)',
+ 'Annotations & Alerts',
+ 'dashboard'
+ )
+ )
+ .addTemplate(
+ g.template.datasource('DS_PROMETHEUS', 'prometheus', 'default', label='Data Source')
+ )
+
+ .addTemplate(
+ $.addTemplateSchema('Cluster',
+ '$DS_PROMETHEUS',
+ 'label_values(ceph_health_status, cluster)',
+ 2,
+ true,
+ 0,
+ null,
+ '',
+ current='All')
+ )
+ .addPanels([
+ $.addRowSchema(false, true, 'Clusters') + { gridPos: { x: 0, y: 1, w: 24, h: 1 } },
+ $.addStatPanel(
+ title='Status',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 0, y: 2, w: 5, h: 7 },
+ graphMode='none',
+ colorMode='value',
+ orientation='auto',
+ justifyMode='center',
+ thresholdsMode='absolute',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'text', value: null },
+ ])
+ .addOverrides(
+ [
+ {
+ matcher: { id: 'byName', options: 'Warning' },
+ properties: [
+ {
+ id: 'thresholds',
+ value: { mode: 'absolute', steps: [{ color: 'text', value: null }, { color: 'semi-dark-yellow', value: 1 }] },
+ },
+ ],
+ },
+ {
+ matcher: { id: 'byName', options: 'Error' },
+ properties: [
+ {
+ id: 'thresholds',
+ value: { mode: 'absolute', steps: [{ color: 'text', value: null }, { color: 'semi-dark-red', value: 1 }] },
+ },
+ ],
+ },
+ {
+ matcher: { id: 'byName', options: 'Healthy' },
+ properties: [
+ {
+ id: 'thresholds',
+ value: { mode: 'absolute', steps: [{ color: 'text', value: null }, { color: 'semi-dark-green', value: 1 }] },
+ },
+ ],
+ },
+ ]
+ )
+ .addTargets([
+ $.addTargetSchema(
+ expr='count(ceph_health_status==0) or vector(0)',
+ datasource='${DS_PROMETHEUS}',
+ legendFormat='Healthy',
+ ),
+ $.addTargetSchema(
+ expr='count(ceph_health_status==1)',
+ datasource='${DS_PROMETHEUS}',
+ legendFormat='Warning'
+ ),
+ $.addTargetSchema(
+ expr='count(ceph_health_status==2)',
+ datasource='${DS_PROMETHEUS}',
+ legendFormat='Error'
+ ),
+ ]),
+
+ $.addTableExtended(
+ datasource='${DS_PROMETHEUS}',
+ title='Details',
+ gridPosition={ h: 7, w: 19, x: 5, y: 2 },
+ options={
+ footer: {
+ fields: '',
+ reducer: ['sum'],
+ countRows: false,
+ enablePagination: false,
+ show: false,
+ },
+ frameIndex: 1,
+ showHeader: true,
+ },
+ custom={ align: 'left', cellOptions: { type: 'color-text' }, filterable: false, inspect: false },
+ thresholds={
+ mode: 'absolute',
+ steps: [
+ { color: 'text' },
+ ],
+ },
+ overrides=[
+ {
+ matcher: { id: 'byName', options: 'Value #A' },
+ properties: [
+ { id: 'mappings', value: [{ options: { '0': { color: 'semi-dark-green', index: 2, text: 'Healthy' }, '1': { color: 'semi-dark-yellow', index: 0, text: 'Warning' }, '2': { color: 'semi-dark-red', index: 1, text: 'Error' } }, type: 'value' }] },
+ ],
+ },
+ {
+ matcher: { id: 'byName', options: 'IOPS' },
+ properties: [
+ { id: 'unit', value: 'ops' },
+ ],
+ },
+ {
+ matcher: { id: 'byName', options: 'Value #E' },
+ properties: [
+ { id: 'unit', value: 'bytes' },
+ ],
+ },
+ {
+ matcher: { id: 'byName', options: 'Capacity Used' },
+ properties: [
+ { id: 'unit', value: 'bytes' },
+ ],
+ },
+ {
+ matcher: { id: 'byName', options: 'Cluster' },
+ properties: [
+ { id: 'links', value: [{ title: '', url: '/d/GQ3MHvnIz/ceph-cluster-new?var-cluster=${__data.fields.Cluster}&${DS_PROMETHEUS:queryparam}' }] },
+ ],
+ },
+ {
+ matcher: { id: 'byName', options: 'Alerts' },
+ properties: [
+ { id: 'mappings', value: [{ options: { match: null, result: { index: 0, text: '0' } }, type: 'special' }] },
+ ],
+ },
+ ],
+ pluginVersion='9.4.7'
+ )
+ .addTransformations([
+ {
+ id: 'joinByField',
+ options: { byField: 'cluster', mode: 'outer' },
+ },
+ {
+ id: 'organize',
+ options: {
+ excludeByName: {
+ 'Time 1': true,
+ 'Time 2': true,
+ 'Time 3': true,
+ 'Time 4': true,
+ 'Time 5': true,
+ 'Time 6': true,
+ 'Value #B': true,
+ '__name__ 1': true,
+ '__name__ 2': true,
+ '__name__ 3': true,
+ ceph_daemon: true,
+ device_class: true,
+ hostname: true,
+ 'instance 1': true,
+ 'instance 2': true,
+ 'instance 3': true,
+ 'job 1': true,
+ 'job 2': true,
+ 'job 3': true,
+ 'replica 1': true,
+ 'replica 2': true,
+ 'replica 3': true,
+ },
+ indexByName: {
+ 'Time 1': 8,
+ 'Time 2': 13,
+ 'Time 3': 21,
+ 'Time 4': 7,
+ 'Time 5': 22,
+ 'Time 6': 23,
+ 'Value #A': 1,
+ 'Value #B': 20,
+ 'Value #C': 3,
+ 'Value #D': 4,
+ 'Value #E': 5,
+ 'Value #F': 6,
+ '__name__ 1': 9,
+ '__name__ 2': 14,
+ '__name__ 3': 24,
+ ceph_daemon: 15,
+ ceph_version: 2,
+ cluster: 0,
+ device_class: 25,
+ hostname: 16,
+ 'instance 1': 10,
+ 'instance 2': 17,
+ 'instance 3': 26,
+ 'job 1': 11,
+ 'job 2': 18,
+ 'job 3': 27,
+ 'replica 1': 12,
+ 'replica 2': 19,
+ 'replica 3': 28,
+ },
+ renameByName: {
+ 'Value #A': 'Status',
+ 'Value #C': 'Alerts',
+ 'Value #D': 'IOPS',
+ 'Value #E': 'Throughput',
+ 'Value #F': 'Capacity Used',
+ ceph_version: 'Version',
+ cluster: 'Cluster',
+ },
+ },
+ },
+ ]).addTargets([
+ $.addTargetSchema(
+ expr='ceph_health_status',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=true,
+ interval='',
+ legendFormat='__auto',
+ range=false,
+ ),
+ $.addTargetSchema(
+ expr='ceph_mgr_metadata',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=true,
+ interval='',
+ legendFormat='__auto',
+ range=false,
+ ),
+ $.addTargetSchema(
+ expr='count(ALERTS{alertstate="firing", cluster=~"$Cluster"})',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=true,
+ interval='',
+ legendFormat='__auto',
+ range=false,
+ ),
+ $.addTargetSchema(
+ expr='sum by (cluster) (irate(ceph_pool_wr[$__interval])) \n+ sum by (cluster) (irate(ceph_pool_rd[$__interval])) ',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=true,
+ interval='',
+ legendFormat='__auto',
+ range=false,
+ ),
+ $.addTargetSchema(
+ expr='sum by (cluster) (irate(ceph_pool_rd_bytes[$__interval]))\n+ sum by (cluster) (irate(ceph_pool_wr_bytes[$__interval])) ',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=true,
+ interval='',
+ legendFormat='__auto',
+ range=false,
+ ),
+ $.addTargetSchema(
+ expr='ceph_cluster_by_class_total_used_bytes',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=true,
+ interval='',
+ legendFormat='__auto',
+ range=false,
+ ),
+ ]),
+
+
+ $.addRowSchema(false, true, 'Overview') + { gridPos: { x: 0, y: 9, w: 24, h: 1 } },
+ $.addStatPanel(
+ title='Cluster Count',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 0, y: 10, w: 3, h: 4 },
+ graphMode='none',
+ colorMode='value',
+ orientation='auto',
+ justifyMode='center',
+ thresholdsMode='absolute',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'text', value: null },
+ { color: 'red', value: 80 },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='count(ceph_health_status{cluster=~"$Cluster"}) or vector(0)',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=true,
+ interval='',
+ legendFormat='__auto',
+ range=false,
+ ),
+ ]),
+
+ $.addGaugePanel(
+ title='Capacity Used',
+ gridPosition={ h: 8, w: 4, x: 3, y: 10 },
+ unit='percentunit',
+ max=1,
+ min=0,
+ interval='1m',
+ pluginVersion='9.4.7'
+ )
+ .addThresholds([
+ { color: 'green', value: null },
+ { color: 'semi-dark-yellow', value: 0.75 },
+ { color: 'red', value: 0.85 },
+ ])
+ .addTarget($.addTargetSchema(
+ expr='sum(ceph_cluster_total_used_bytes{cluster=~"$Cluster"}) / sum(ceph_cluster_total_bytes{cluster=~"$Cluster"})',
+ instant=true,
+ legendFormat='Used',
+ datasource='${DS_PROMETHEUS}',
+ )),
+
+ $.addStatPanel(
+ title='Total Capacity',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 7, y: 10, w: 3, h: 4 },
+ graphMode='area',
+ colorMode='none',
+ orientation='auto',
+ justifyMode='auto',
+ thresholdsMode='absolute',
+ unit='bytes',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'green', value: null },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='sum(ceph_cluster_total_bytes{cluster=~"$Cluster"})',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=false,
+ interval='',
+ legendFormat='__auto',
+ range=true,
+ ),
+ ]),
+
+ $.addStatPanel(
+ title='OSDs',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 10, y: 10, w: 3, h: 4 },
+ graphMode='area',
+ colorMode='none',
+ orientation='auto',
+ justifyMode='auto',
+ thresholdsMode='absolute',
+ unit='none',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'green', value: null },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='count(ceph_osd_metadata{cluster=~"$Cluster"})',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=false,
+ interval='',
+ legendFormat='__auto',
+ range=true,
+ ),
+ ]),
+
+ $.addStatPanel(
+ title='Hosts',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 13, y: 10, w: 3, h: 4 },
+ graphMode='area',
+ colorMode='none',
+ orientation='auto',
+ justifyMode='auto',
+ thresholdsMode='absolute',
+ unit='none',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'green', value: null },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='count(sum by (hostname) (ceph_osd_metadata{cluster=~"$Cluster"}))',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=false,
+ interval='',
+ legendFormat='__auto',
+ range=true,
+ ),
+ ]),
+
+ $.addStatPanel(
+ title='Client IOPS',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 16, y: 10, w: 4, h: 4 },
+ graphMode='area',
+ colorMode='none',
+ orientation='auto',
+ justifyMode='center',
+ thresholdsMode='absolute',
+ unit='ops',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'green', value: null },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='sum(irate(ceph_pool_wr{cluster=~"$Cluster"}[$__interval]))',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ hide=false,
+ exemplar=false,
+ instant=false,
+ legendFormat='Write',
+ range=true,
+ ),
+ $.addTargetSchema(
+ expr='sum(irate(ceph_pool_rd{cluster=~"$Cluster"}[$__interval]))',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ hide=false,
+ exemplar=false,
+ legendFormat='Read',
+ range=true,
+ ),
+ ]),
+
+ $.addStatPanel(
+ title='OSD Latencies',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 20, y: 10, w: 4, h: 4 },
+ graphMode='area',
+ colorMode='none',
+ orientation='auto',
+ justifyMode='center',
+ thresholdsMode='absolute',
+ unit='ms',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'green', value: null },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='avg(ceph_osd_apply_latency_ms{cluster=~"$Cluster"})',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ hide=false,
+ exemplar=false,
+ instant=false,
+ legendFormat='Apply',
+ range=true,
+ ),
+ $.addTargetSchema(
+ expr='avg(ceph_osd_commit_latency_ms{cluster=~"$Cluster"})',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ hide=false,
+ exemplar=false,
+ legendFormat='Commit',
+ range=true,
+ ),
+ ]),
+
+ $.addStatPanel(
+ title='Alert Count',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 0, y: 14, w: 3, h: 4 },
+ graphMode='none',
+ colorMode='value',
+ orientation='auto',
+ justifyMode='center',
+ thresholdsMode='absolute',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'text', value: null },
+ { color: 'red', value: 80 },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='count(ALERTS{alertstate="firing", cluster=~"$Cluster"}) or vector(0)',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=true,
+ interval='',
+ legendFormat='__auto',
+ range=false,
+ ),
+ ]),
+
+ $.addStatPanel(
+ title='Total Used',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 7, y: 14, w: 3, h: 4 },
+ graphMode='area',
+ colorMode='none',
+ orientation='auto',
+ justifyMode='auto',
+ thresholdsMode='absolute',
+ unit='bytes',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'green', value: null },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='sum(ceph_cluster_total_used_bytes{cluster=~"$Cluster"})',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=false,
+ interval='',
+ legendFormat='__auto',
+ range=true,
+ ),
+ ]),
+
+ $.addStatPanel(
+ title='Capacity Prediction',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 10, y: 14, w: 3, h: 4 },
+ graphMode='none',
+ colorMode='none',
+ orientation='auto',
+ justifyMode='auto',
+ unit='s',
+ thresholdsMode='absolute',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'green', value: null },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='predict_linear(avg(increase(ceph_cluster_total_used_bytes{cluster=~"${Cluster}"}[1d]))[7d:1h],120)',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ hide=false,
+ exemplar=false,
+ legendFormat='__auto',
+ range=true,
+ ),
+ ]),
+
+ $.addStatPanel(
+ title='Pools',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 13, y: 14, w: 3, h: 4 },
+ graphMode='area',
+ colorMode='none',
+ orientation='auto',
+ justifyMode='auto',
+ thresholdsMode='absolute',
+ unit='none',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'green', value: null },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='count(ceph_pool_metadata{cluster=~"$Cluster"})',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=false,
+ interval='',
+ legendFormat='__auto',
+ range=true,
+ ),
+ ]),
+
+ $.addStatPanel(
+ title='Client Bandwidth',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 16, y: 14, w: 4, h: 4 },
+ graphMode='area',
+ colorMode='none',
+ orientation='auto',
+ justifyMode='center',
+ thresholdsMode='absolute',
+ unit='binBps',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'green', value: null },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='sum(irate(ceph_pool_rd_bytes{cluster=~"$Cluster"}[$__interval]))',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ hide=false,
+ exemplar=false,
+ instant=false,
+ legendFormat='Write',
+ range=true,
+ ),
+ $.addTargetSchema(
+ expr='sum(irate(ceph_pool_wr_bytes{cluster=~"$Cluster"}[$__interval]))',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ hide=false,
+ exemplar=false,
+ legendFormat='Read',
+ range=true,
+ ),
+ ]),
+
+ $.addStatPanel(
+ title='Recovery Rate',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 20, y: 14, w: 4, h: 4 },
+ graphMode='area',
+ colorMode='none',
+ orientation='auto',
+ justifyMode='center',
+ thresholdsMode='absolute',
+ unit='binBps',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'green', value: null },
+ ])
+ .addTargets([
+ $.addTargetSchema(
+ expr='sum(irate(ceph_osd_recovery_ops{cluster=~"$Cluster"}[$__interval]))',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ hide=false,
+ exemplar=false,
+ instant=false,
+ legendFormat='Write',
+ range=true,
+ ),
+ ]),
+
+
+ $.addRowSchema(false, true, 'Alerts', collapsed=true)
+ .addPanels([
+ $.addStatPanel(
+ title='Status',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ x: 0, y: 19, w: 5, h: 7 },
+ graphMode='area',
+ colorMode='value',
+ orientation='auto',
+ justifyMode='center',
+ thresholdsMode='absolute',
+ pluginVersion='9.4.7',
+ ).addThresholds([
+ { color: 'text', value: null },
+ ])
+ .addOverrides(
+ [
+ {
+ matcher: { id: 'byName', options: 'Critical' },
+ properties: [
+ {
+ id: 'thresholds',
+ value: { mode: 'absolute', steps: [{ color: 'text', value: null }, { color: 'semi-dark-red', value: 1 }] },
+ },
+ ],
+ },
+ {
+ matcher: { id: 'byName', options: 'Warning' },
+ properties: [
+ {
+ id: 'thresholds',
+ value: { mode: 'absolute', steps: [{ color: 'text', value: null }, { color: 'semi-dark-yellow', value: 1 }] },
+ },
+ ],
+ },
+ ]
+ )
+ .addTargets([
+ $.addTargetSchema(
+ expr='count(ALERTS{alertstate="firing",severity="critical", cluster=~"$Cluster"}) OR vector(0)',
+ datasource='${DS_PROMETHEUS}',
+ legendFormat='Critical',
+ instant=true,
+ range=false
+ ),
+ $.addTargetSchema(
+ expr='count(ALERTS{alertstate="firing",severity="warning", cluster=~"$Cluster"}) OR vector(0)',
+ datasource='${DS_PROMETHEUS}',
+ legendFormat='Warning',
+ instant=true,
+ range=false
+ ),
+ ]),
+
+
+ $.addTableExtended(
+ datasource='${DS_PROMETHEUS}',
+ title='Alerts',
+ gridPosition={ h: 7, w: 19, x: 5, y: 19 },
+ options={
+ footer: {
+ fields: '',
+ reducer: ['sum'],
+ countRows: false,
+ enablePagination: false,
+ show: false,
+ },
+ frameIndex: 1,
+ showHeader: true,
+ sortBy: [{ desc: false, displayName: 'Severity' }],
+ },
+ custom={ align: 'auto', cellOptions: { type: 'auto' }, filterable: true, inspect: false },
+ thresholds={
+ mode: 'absolute',
+ steps: [
+ { color: 'green' },
+ { color: 'red', value: 80 },
+ ],
+ },
+ pluginVersion='9.4.7'
+ )
+ .addTransformations([
+ {
+ id: 'joinByField',
+ options: { byField: 'cluster', mode: 'outer' },
+ },
+ {
+ id: 'organize',
+ options: {
+ excludeByName: {
+ Time: true,
+ Value: true,
+ __name__: true,
+ instance: true,
+ job: true,
+ oid: true,
+ replica: true,
+ type: true,
+ },
+ indexByName: {
+ Time: 0,
+ Value: 9,
+ __name__: 1,
+ alertname: 2,
+ alertstate: 4,
+ cluster: 3,
+ instance: 6,
+ job: 7,
+ severity: 5,
+ type: 8,
+ },
+ renameByName: {
+ alertname: 'Name',
+ alertstate: 'State',
+ cluster: 'Cluster',
+ severity: 'Severity',
+ },
+ },
+ },
+ ]).addTargets([
+ $.addTargetSchema(
+ expr='ALERTS{alertstate="firing", cluster=~"$Cluster"}',
+ datasource={ type: 'prometheus', uid: '${DS_PROMETHEUS}' },
+ format='table',
+ hide=false,
+ exemplar=false,
+ instant=true,
+ interval='',
+ legendFormat='__auto',
+ range=false,
+ ),
+ ]),
+
+ $.addAlertListPanel(
+ title='Alerts(Grouped)',
+ datasource={
+ type: 'datasource',
+ uid: 'grafana',
+ },
+ gridPosition={ h: 8, w: 24, x: 0, y: 26 },
+ alertName='',
+ dashboardAlerts=false,
+ groupBy=[],
+ groupMode='default',
+ maxItems=20,
+ sortOrder=1,
+ stateFilter={
+ 'error': true,
+ firing: true,
+ noData: false,
+ normal: false,
+ pending: true,
+ },
+ ),
+ ]) + { gridPos: { x: 0, y: 18, w: 24, h: 1 } },
+
+ $.addRowSchema(false, true, 'Cluster Stats', collapsed=true)
+ .addPanels([
+ $.timeSeriesPanel(
+ lineInterpolation='linear',
+ lineWidth=1,
+ drawStyle='line',
+ axisPlacement='auto',
+ title='Top 5 - Capacity Utilization(%)',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ h: 7, w: 8, x: 0, y: 30 },
+ fillOpacity=0,
+ pointSize=5,
+ showPoints='auto',
+ unit='percentunit',
+ displayMode='table',
+ showLegend=true,
+ placement='bottom',
+ tooltip={ mode: 'multi', sort: 'desc' },
+ stackingMode='none',
+ spanNulls=false,
+ decimals=2,
+ thresholdsMode='percentage',
+ sortBy='Last',
+ sortDesc=true
+ )
+ .addCalcs(['last'])
+ .addThresholds([
+ { color: 'green' },
+ ])
+ .addTargets(
+ [
+ $.addTargetSchema(
+ expr='topk(5, ceph_cluster_total_used_bytes/ceph_cluster_total_bytes)',
+ datasource='${DS_PROMETHEUS}',
+ instant=false,
+ legendFormat='{{cluster}}',
+ step=300,
+ range=true,
+ ),
+ ]
+ ),
+
+
+ $.timeSeriesPanel(
+ lineInterpolation='linear',
+ lineWidth=1,
+ drawStyle='line',
+ axisPlacement='auto',
+ title='Top 5 - Cluster IOPS',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ h: 7, w: 8, x: 8, y: 30 },
+ fillOpacity=0,
+ pointSize=5,
+ showPoints='auto',
+ unit='ops',
+ displayMode='table',
+ showLegend=true,
+ placement='bottom',
+ tooltip={ mode: 'multi', sort: 'desc' },
+ stackingMode='none',
+ spanNulls=false,
+ decimals=2,
+ thresholdsMode='percentage',
+ sortBy='Last',
+ sortDesc=true
+ )
+ .addCalcs(['last'])
+ .addThresholds([
+ { color: 'green' },
+ ])
+ .addTargets(
+ [
+ $.addTargetSchema(
+ expr='topk(10, sum by (cluster) (irate(ceph_osd_op_w[$__interval])) \n+ sum by (cluster) (irate(ceph_osd_op_r[$__interval])) )',
+ datasource='${DS_PROMETHEUS}',
+ instant=false,
+ legendFormat='{{cluster}}',
+ step=300,
+ range=true,
+ ),
+ ]
+ ),
+
+
+ $.timeSeriesPanel(
+ lineInterpolation='linear',
+ lineWidth=1,
+ drawStyle='line',
+ axisPlacement='auto',
+ title='Top 10 - Capacity Utilization(%) by Pool',
+ datasource='${DS_PROMETHEUS}',
+ gridPosition={ h: 7, w: 8, x: 16, y: 30 },
+ fillOpacity=0,
+ pointSize=5,
+ showPoints='auto',
+ unit='percentunit',
+ displayMode='table',
+ showLegend=true,
+ placement='bottom',
+ tooltip={ mode: 'multi', sort: 'desc' },
+ stackingMode='none',
+ spanNulls=false,
+ decimals=2,
+ thresholdsMode='absolute',
+ sortBy='Last',
+ sortDesc=true
+ )
+ .addCalcs(['last'])
+ .addThresholds([
+ { color: 'green' },
+ ])
+ .addTargets(
+ [
+ $.addTargetSchema(
+ expr='topk(10, ceph_pool_bytes_used{cluster=~"$Cluster"}/ceph_pool_max_avail{cluster=~"$Cluster"} * on(pool_id, cluster) group_left(instance, name) ceph_pool_metadata{cluster=~"$Cluster"})',
+ datasource='${DS_PROMETHEUS}',
+ instant=false,
+ legendFormat='{{cluster}} - {{name}}',
+ step=300,
+ range=true,
+ ),
+ ]
+ ),
+ ]) + { gridPos: { x: 0, y: 29, w: 24, h: 1 } },
+ ]),
+}
regex,
hide='',
multi=false,
- allValues=null)::
+ allValues=null,
+ current=null)::
g.template.new(name=name,
datasource=datasource,
query=query,
regex=regex,
hide=hide,
multi=multi,
- allValues=allValues),
+ allValues=allValues,
+ current=current),
addAnnotationSchema(builtIn,
datasource,
--- /dev/null
+{
+ "__inputs": [ ],
+ "__requires": [ ],
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": "-- Grafana --",
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "showIn": 0,
+ "tags": [ ],
+ "type": "dashboard"
+ }
+ ]
+ },
+ "description": "",
+ "editable": false,
+ "gnetId": null,
+ "graphTooltip": 0,
+ "hideControls": false,
+ "id": null,
+ "links": [ ],
+ "panels": [
+ {
+ "collapse": false,
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 1
+ },
+ "id": 2,
+ "panels": [ ],
+ "repeat": null,
+ "repeatIteration": null,
+ "repeatRowId": null,
+ "showTitle": true,
+ "title": "Clusters",
+ "titleSize": "h6",
+ "type": "row"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "text",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Warning"
+ },
+ "properties": [
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "text",
+ "value": null
+ },
+ {
+ "color": "semi-dark-yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Error"
+ },
+ "properties": [
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "text",
+ "value": null
+ },
+ {
+ "color": "semi-dark-red",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Healthy"
+ },
+ "properties": [
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "text",
+ "value": null
+ },
+ {
+ "color": "semi-dark-green",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 5,
+ "x": 0,
+ "y": 2
+ },
+ "id": 3,
+ "links": [ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "center",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "expr": "count(ceph_health_status==0) or vector(0)",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "Healthy",
+ "refId": "A"
+ },
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "expr": "count(ceph_health_status==1)",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "Warning",
+ "refId": "B"
+ },
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "expr": "count(ceph_health_status==2)",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "legendFormat": "Error",
+ "refId": "C"
+ }
+ ],
+ "title": "Status",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "columns": [ ],
+ "datasource": "${DS_PROMETHEUS}",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {
+ "align": "left",
+ "cellOptions": {
+ "type": "color-text"
+ },
+ "filterable": false,
+ "inspect": false
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "text"
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Value #A"
+ },
+ "properties": [
+ {
+ "id": "mappings",
+ "value": [
+ {
+ "options": {
+ "0": {
+ "color": "semi-dark-green",
+ "index": 2,
+ "text": "Healthy"
+ },
+ "1": {
+ "color": "semi-dark-yellow",
+ "index": 0,
+ "text": "Warning"
+ },
+ "2": {
+ "color": "semi-dark-red",
+ "index": 1,
+ "text": "Error"
+ }
+ },
+ "type": "value"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "IOPS"
+ },
+ "properties": [
+ {
+ "id": "unit",
+ "value": "ops"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Value #E"
+ },
+ "properties": [
+ {
+ "id": "unit",
+ "value": "bytes"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Capacity Used"
+ },
+ "properties": [
+ {
+ "id": "unit",
+ "value": "bytes"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Cluster"
+ },
+ "properties": [
+ {
+ "id": "links",
+ "value": [
+ {
+ "title": "",
+ "url": "/d/GQ3MHvnIz/ceph-cluster-new?var-cluster=${__data.fields.Cluster}&${DS_PROMETHEUS:queryparam}"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Alerts"
+ },
+ "properties": [
+ {
+ "id": "mappings",
+ "value": [
+ {
+ "options": {
+ "match": null,
+ "result": {
+ "index": 0,
+ "text": "0"
+ }
+ },
+ "type": "special"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 19,
+ "x": 5,
+ "y": 2
+ },
+ "id": 4,
+ "links": [ ],
+ "options": {
+ "footer": {
+ "countRows": false,
+ "enablePagination": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "frameIndex": 1,
+ "showHeader": true
+ },
+ "pluginVersion": "9.4.7",
+ "styles": "",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "ceph_health_status",
+ "format": "table",
+ "hide": false,
+ "instant": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "ceph_mgr_metadata",
+ "format": "table",
+ "hide": false,
+ "instant": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "count(ALERTS{alertstate=\"firing\", cluster=~\"$Cluster\"})",
+ "format": "table",
+ "hide": false,
+ "instant": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "C"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "sum by (cluster) (irate(ceph_pool_wr[$__interval])) \n+ sum by (cluster) (irate(ceph_pool_rd[$__interval])) ",
+ "format": "table",
+ "hide": false,
+ "instant": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "D"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "sum by (cluster) (irate(ceph_pool_rd_bytes[$__interval]))\n+ sum by (cluster) (irate(ceph_pool_wr_bytes[$__interval])) ",
+ "format": "table",
+ "hide": false,
+ "instant": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "E"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "ceph_cluster_by_class_total_used_bytes",
+ "format": "table",
+ "hide": false,
+ "instant": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "F"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Details",
+ "transformations": [
+ {
+ "id": "joinByField",
+ "options": {
+ "byField": "cluster",
+ "mode": "outer"
+ }
+ },
+ {
+ "id": "organize",
+ "options": {
+ "excludeByName": {
+ "Time 1": true,
+ "Time 2": true,
+ "Time 3": true,
+ "Time 4": true,
+ "Time 5": true,
+ "Time 6": true,
+ "Value #B": true,
+ "__name__ 1": true,
+ "__name__ 2": true,
+ "__name__ 3": true,
+ "ceph_daemon": true,
+ "device_class": true,
+ "hostname": true,
+ "instance 1": true,
+ "instance 2": true,
+ "instance 3": true,
+ "job 1": true,
+ "job 2": true,
+ "job 3": true,
+ "replica 1": true,
+ "replica 2": true,
+ "replica 3": true
+ },
+ "indexByName": {
+ "Time 1": 8,
+ "Time 2": 13,
+ "Time 3": 21,
+ "Time 4": 7,
+ "Time 5": 22,
+ "Time 6": 23,
+ "Value #A": 1,
+ "Value #B": 20,
+ "Value #C": 3,
+ "Value #D": 4,
+ "Value #E": 5,
+ "Value #F": 6,
+ "__name__ 1": 9,
+ "__name__ 2": 14,
+ "__name__ 3": 24,
+ "ceph_daemon": 15,
+ "ceph_version": 2,
+ "cluster": 0,
+ "device_class": 25,
+ "hostname": 16,
+ "instance 1": 10,
+ "instance 2": 17,
+ "instance 3": 26,
+ "job 1": 11,
+ "job 2": 18,
+ "job 3": 27,
+ "replica 1": 12,
+ "replica 2": 19,
+ "replica 3": 28
+ },
+ "renameByName": {
+ "Value #A": "Status",
+ "Value #C": "Alerts",
+ "Value #D": "IOPS",
+ "Value #E": "Throughput",
+ "Value #F": "Capacity Used",
+ "ceph_version": "Version",
+ "cluster": "Cluster"
+ }
+ }
+ }
+ ],
+ "type": "table"
+ },
+ {
+ "collapse": false,
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 9
+ },
+ "id": 5,
+ "panels": [ ],
+ "repeat": null,
+ "repeatIteration": null,
+ "repeatRowId": null,
+ "showTitle": true,
+ "title": "Overview",
+ "titleSize": "h6",
+ "type": "row"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "text",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "none"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 0,
+ "y": 10
+ },
+ "id": 6,
+ "links": [ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "center",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "count(ceph_health_status{cluster=~\"$Cluster\"}) or vector(0)",
+ "format": "table",
+ "hide": false,
+ "instant": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "A"
+ }
+ ],
+ "title": "Cluster Count",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "links": [ ],
+ "mappings": [ ],
+ "max": 1,
+ "min": 0,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "semi-dark-yellow",
+ "value": 0.75
+ },
+ {
+ "color": "red",
+ "value": 0.84999999999999998
+ }
+ ]
+ },
+ "unit": "percentunit"
+ }
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 4,
+ "x": 3,
+ "y": 10
+ },
+ "id": 7,
+ "interval": "1m",
+ "links": [ ],
+ "maxDataPoints": 100,
+ "options": {
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "expr": "sum(ceph_cluster_total_used_bytes{cluster=~\"$Cluster\"}) / sum(ceph_cluster_total_bytes{cluster=~\"$Cluster\"})",
+ "format": "time_series",
+ "instant": true,
+ "intervalFactor": 1,
+ "legendFormat": "Used",
+ "refId": "A"
+ }
+ ],
+ "title": "Capacity Used",
+ "transparent": false,
+ "type": "gauge"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "bytes"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 7,
+ "y": 10
+ },
+ "id": 8,
+ "links": [ ],
+ "options": {
+ "colorMode": "none",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "sum(ceph_cluster_total_bytes{cluster=~\"$Cluster\"})",
+ "format": "table",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Total Capacity",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 10,
+ "y": 10
+ },
+ "id": 9,
+ "links": [ ],
+ "options": {
+ "colorMode": "none",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "count(ceph_osd_metadata{cluster=~\"$Cluster\"})",
+ "format": "table",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "OSDs",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 13,
+ "y": 10
+ },
+ "id": 10,
+ "links": [ ],
+ "options": {
+ "colorMode": "none",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "count(sum by (hostname) (ceph_osd_metadata{cluster=~\"$Cluster\"}))",
+ "format": "table",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Hosts",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "ops"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 4,
+ "x": 16,
+ "y": 10
+ },
+ "id": 11,
+ "links": [ ],
+ "options": {
+ "colorMode": "none",
+ "graphMode": "area",
+ "justifyMode": "center",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "sum(irate(ceph_pool_wr{cluster=~\"$Cluster\"}[$__interval]))",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "Write",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "sum(irate(ceph_pool_rd{cluster=~\"$Cluster\"}[$__interval]))",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "Read",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "Client IOPS",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "ms"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 4,
+ "x": 20,
+ "y": 10
+ },
+ "id": 12,
+ "links": [ ],
+ "options": {
+ "colorMode": "none",
+ "graphMode": "area",
+ "justifyMode": "center",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "avg(ceph_osd_apply_latency_ms{cluster=~\"$Cluster\"})",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "Apply",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "avg(ceph_osd_commit_latency_ms{cluster=~\"$Cluster\"})",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "Commit",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "OSD Latencies",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "text",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "none"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 0,
+ "y": 14
+ },
+ "id": 13,
+ "links": [ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "center",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "count(ALERTS{alertstate=\"firing\", cluster=~\"$Cluster\"}) or vector(0)",
+ "format": "table",
+ "hide": false,
+ "instant": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "A"
+ }
+ ],
+ "title": "Alert Count",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "bytes"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 7,
+ "y": 14
+ },
+ "id": 14,
+ "links": [ ],
+ "options": {
+ "colorMode": "none",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "sum(ceph_cluster_total_used_bytes{cluster=~\"$Cluster\"})",
+ "format": "table",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Total Used",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "s"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 10,
+ "y": 14
+ },
+ "id": 15,
+ "links": [ ],
+ "options": {
+ "colorMode": "none",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "predict_linear(avg(increase(ceph_cluster_total_used_bytes{cluster=~\"${Cluster}\"}[1d]))[7d:1h],120)",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Capacity Prediction",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 13,
+ "y": 14
+ },
+ "id": 16,
+ "links": [ ],
+ "options": {
+ "colorMode": "none",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "count(ceph_pool_metadata{cluster=~\"$Cluster\"})",
+ "format": "table",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Pools",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "binBps"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 4,
+ "x": 16,
+ "y": 14
+ },
+ "id": 17,
+ "links": [ ],
+ "options": {
+ "colorMode": "none",
+ "graphMode": "area",
+ "justifyMode": "center",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "sum(irate(ceph_pool_rd_bytes{cluster=~\"$Cluster\"}[$__interval]))",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "Write",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "sum(irate(ceph_pool_wr_bytes{cluster=~\"$Cluster\"}[$__interval]))",
+ "format": "time_series",
+ "hide": false,
+ "intervalFactor": 1,
+ "legendFormat": "Read",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "Client Bandwidth",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "binBps"
+ }
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 4,
+ "x": 20,
+ "y": 14
+ },
+ "id": 18,
+ "links": [ ],
+ "options": {
+ "colorMode": "none",
+ "graphMode": "area",
+ "justifyMode": "center",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "sum(irate(ceph_osd_recovery_ops{cluster=~\"$Cluster\"}[$__interval]))",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "Write",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Recovery Rate",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "collapse": false,
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 18
+ },
+ "id": 19,
+ "panels": [
+ {
+ "colors": null,
+ "datasource": "${DS_PROMETHEUS}",
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "decimals": 0,
+ "links": [ ],
+ "mappings": [ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "text",
+ "value": null
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Critical"
+ },
+ "properties": [
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "text",
+ "value": null
+ },
+ {
+ "color": "semi-dark-red",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Warning"
+ },
+ "properties": [
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "text",
+ "value": null
+ },
+ {
+ "color": "semi-dark-yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 5,
+ "x": 0,
+ "y": 19
+ },
+ "id": 20,
+ "links": [ ],
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "center",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "expr": "count(ALERTS{alertstate=\"firing\",severity=\"critical\", cluster=~\"$Cluster\"}) OR vector(0)",
+ "format": "time_series",
+ "instant": true,
+ "intervalFactor": 1,
+ "legendFormat": "Critical",
+ "range": false,
+ "refId": "A"
+ },
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "expr": "count(ALERTS{alertstate=\"firing\",severity=\"warning\", cluster=~\"$Cluster\"}) OR vector(0)",
+ "format": "time_series",
+ "instant": true,
+ "intervalFactor": 1,
+ "legendFormat": "Warning",
+ "range": false,
+ "refId": "B"
+ }
+ ],
+ "title": "Status",
+ "transparent": false,
+ "type": "stat"
+ },
+ {
+ "columns": [ ],
+ "datasource": "${DS_PROMETHEUS}",
+ "fieldConfig": {
+ "defaults": {
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "type": "auto"
+ },
+ "filterable": true,
+ "inspect": false
+ },
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": [ ]
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 19,
+ "x": 5,
+ "y": 19
+ },
+ "id": 21,
+ "links": [ ],
+ "options": {
+ "footer": {
+ "countRows": false,
+ "enablePagination": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "frameIndex": 1,
+ "showHeader": true,
+ "sortBy": [
+ {
+ "desc": false,
+ "displayName": "Severity"
+ }
+ ]
+ },
+ "pluginVersion": "9.4.7",
+ "styles": "",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "exemplar": false,
+ "expr": "ALERTS{alertstate=\"firing\", cluster=~\"$Cluster\"}",
+ "format": "table",
+ "hide": false,
+ "instant": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "__auto",
+ "range": false,
+ "refId": "A"
+ }
+ ],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Alerts",
+ "transformations": [
+ {
+ "id": "joinByField",
+ "options": {
+ "byField": "cluster",
+ "mode": "outer"
+ }
+ },
+ {
+ "id": "organize",
+ "options": {
+ "excludeByName": {
+ "Time": true,
+ "Value": true,
+ "__name__": true,
+ "instance": true,
+ "job": true,
+ "oid": true,
+ "replica": true,
+ "type": true
+ },
+ "indexByName": {
+ "Time": 0,
+ "Value": 9,
+ "__name__": 1,
+ "alertname": 2,
+ "alertstate": 4,
+ "cluster": 3,
+ "instance": 6,
+ "job": 7,
+ "severity": 5,
+ "type": 8
+ },
+ "renameByName": {
+ "alertname": "Name",
+ "alertstate": "State",
+ "cluster": "Cluster",
+ "severity": "Severity"
+ }
+ }
+ }
+ ],
+ "type": "table"
+ },
+ {
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 24,
+ "x": 0,
+ "y": 26
+ },
+ "id": 22,
+ "limit": 10,
+ "onlyAlertsOnDashboard": true,
+ "options": {
+ "alertName": "",
+ "dashboardAlerts": false,
+ "groupBy": [ ],
+ "groupMode": "default",
+ "maxItems": 20,
+ "sortOrder": 1,
+ "stateFilter": {
+ "error": true,
+ "firing": true,
+ "noData": false,
+ "normal": false,
+ "pending": true
+ },
+ "viewMode": "list"
+ },
+ "show": "current",
+ "sortOrder": 1,
+ "stateFilter": [ ],
+ "title": "Alerts(Grouped)",
+ "type": "alertlist"
+ }
+ ],
+ "repeat": null,
+ "repeatIteration": null,
+ "repeatRowId": null,
+ "showTitle": true,
+ "title": "Alerts",
+ "titleSize": "h6",
+ "type": "row"
+ },
+ {
+ "collapse": false,
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 29
+ },
+ "id": 23,
+ "panels": [
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 2,
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ },
+ "unit": "percentunit"
+ },
+ "overrides": [ ]
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 0,
+ "y": 30
+ },
+ "id": 24,
+ "options": {
+ "legend": {
+ "calcs": [
+ "last"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true,
+ "sortBy": "Last",
+ "sortDesc": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "desc"
+ }
+ },
+ "pluginVersion": "9.1.3",
+ "targets": [
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "expr": "topk(5, ceph_cluster_total_used_bytes/ceph_cluster_total_bytes)",
+ "format": "time_series",
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "{{cluster}}",
+ "range": true,
+ "refId": "A",
+ "step": 300
+ }
+ ],
+ "title": "Top 5 - Capacity Utilization(%)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 2,
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ },
+ "unit": "ops"
+ },
+ "overrides": [ ]
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 8,
+ "y": 30
+ },
+ "id": 25,
+ "options": {
+ "legend": {
+ "calcs": [
+ "last"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true,
+ "sortBy": "Last",
+ "sortDesc": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "desc"
+ }
+ },
+ "pluginVersion": "9.1.3",
+ "targets": [
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "expr": "topk(10, sum by (cluster) (irate(ceph_osd_op_w[$__interval])) \n+ sum by (cluster) (irate(ceph_osd_op_r[$__interval])) )",
+ "format": "time_series",
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "{{cluster}}",
+ "range": true,
+ "refId": "A",
+ "step": 300
+ }
+ ],
+ "title": "Top 5 - Cluster IOPS",
+ "type": "timeseries"
+ },
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 2,
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ },
+ "unit": "percentunit"
+ },
+ "overrides": [ ]
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 8,
+ "x": 16,
+ "y": 30
+ },
+ "id": 26,
+ "options": {
+ "legend": {
+ "calcs": [
+ "last"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true,
+ "sortBy": "Last",
+ "sortDesc": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "desc"
+ }
+ },
+ "pluginVersion": "9.1.3",
+ "targets": [
+ {
+ "datasource": "${DS_PROMETHEUS}",
+ "expr": "topk(10, ceph_pool_bytes_used{cluster=~\"$Cluster\"}/ceph_pool_max_avail{cluster=~\"$Cluster\"} * on(pool_id, cluster) group_left(instance, name) ceph_pool_metadata{cluster=~\"$Cluster\"})",
+ "format": "time_series",
+ "instant": false,
+ "intervalFactor": 1,
+ "legendFormat": "{{cluster}} - {{name}}",
+ "range": true,
+ "refId": "A",
+ "step": 300
+ }
+ ],
+ "title": "Top 10 - Capacity Utilization(%) by Pool",
+ "type": "timeseries"
+ }
+ ],
+ "repeat": null,
+ "repeatIteration": null,
+ "repeatRowId": null,
+ "showTitle": true,
+ "title": "Cluster Stats",
+ "titleSize": "h6",
+ "type": "row"
+ }
+ ],
+ "refresh": "30s",
+ "rows": [ ],
+ "schemaVersion": 22,
+ "style": "dark",
+ "tags": [
+ "ceph-mixin"
+ ],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "text": "default",
+ "value": "default"
+ },
+ "hide": 0,
+ "label": "Data Source",
+ "name": "DS_PROMETHEUS",
+ "options": [ ],
+ "query": "prometheus",
+ "refresh": 1,
+ "regex": "",
+ "type": "datasource"
+ },
+ {
+ "allValue": null,
+ "current": {
+ "text": "All",
+ "value": "All"
+ },
+ "datasource": "$DS_PROMETHEUS",
+ "hide": 0,
+ "includeAll": true,
+ "label": null,
+ "multi": false,
+ "name": "Cluster",
+ "options": [ ],
+ "query": "label_values(ceph_health_status, cluster)",
+ "refresh": 2,
+ "regex": "",
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tags": [ ],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ }
+ ]
+ },
+ "time": {
+ "from": "now-1h",
+ "to": "now"
+ },
+ "timepicker": {
+ "refresh_intervals": [
+ "5s",
+ "10s",
+ "30s",
+ "1m",
+ "5m",
+ "15m",
+ "30m",
+ "1h",
+ "2h",
+ "1d"
+ ],
+ "time_options": [
+ "5m",
+ "15m",
+ "1h",
+ "6h",
+ "12h",
+ "24h",
+ "2d",
+ "7d",
+ "30d"
+ ]
+ },
+ "timezone": "",
+ "title": "Ceph - Multi-cluster",
+ "uid": "BnxelG7Sz",
+ "version": 0
+}
# -*- coding: utf-8 -*-
+import base64
import json
+import time
import requests
from ..security import Scope
from ..settings import Settings
from ..tools import configure_cors
-from . import APIDoc, APIRouter, CreatePermission, Endpoint, EndpointDoc, \
- ReadPermission, RESTController, UIRouter, UpdatePermission
+from . import APIDoc, APIRouter, CreatePermission, DeletePermission, Endpoint, \
+ EndpointDoc, ReadPermission, RESTController, UIRouter, UpdatePermission
@APIRouter('/multi-cluster', Scope.CONFIG_OPT)
class MultiCluster(RESTController):
def _proxy(self, method, base_url, path, params=None, payload=None, verify=False,
token=None):
+ if not base_url.endswith('/'):
+ base_url = base_url + '/'
try:
if token:
headers = {
@CreatePermission
@EndpointDoc("Authenticate to a remote cluster")
def auth(self, url: str, cluster_alias: str, username=None,
- password=None, token=None, hub_url=None):
-
- multi_cluster_config = self.load_multi_cluster_config()
-
- if not url.endswith('/'):
- url = url + '/'
+ password=None, token=None, hub_url=None, cluster_fsid=None):
if username and password:
payload = {
http_status_code=400,
component='dashboard')
- token = content['token']
+ cluster_token = content['token']
- if token:
self._proxy('PUT', url, 'ui-api/multi-cluster/set_cors_endpoint',
- payload={'url': hub_url}, token=token)
- fsid = self._proxy('GET', url, 'api/health/get_cluster_fsid', token=token)
- content = self._proxy('POST', url, 'api/auth/check', payload={'token': token},
- token=token)
- if 'username' in content:
- username = content['username']
-
- if 'config' not in multi_cluster_config:
- multi_cluster_config['config'] = {}
-
- if fsid in multi_cluster_config['config']:
- existing_entries = multi_cluster_config['config'][fsid]
- if not any(entry['user'] == username for entry in existing_entries):
- existing_entries.append({
- "name": fsid,
- "url": url,
- "cluster_alias": cluster_alias,
- "user": username,
- "token": token,
- })
- else:
- multi_cluster_config['current_user'] = username
- multi_cluster_config['config'][fsid] = [{
+ payload={'url': hub_url}, token=cluster_token)
+
+ fsid = self._proxy('GET', url, 'api/health/get_cluster_fsid', token=cluster_token)
+
+ self.set_multi_cluster_config(fsid, username, url, cluster_alias, cluster_token)
+
+ if token and cluster_fsid and username:
+ self.set_multi_cluster_config(cluster_fsid, username, url, cluster_alias, token)
+
+ def set_multi_cluster_config(self, fsid, username, url, cluster_alias, token):
+ multi_cluster_config = self.load_multi_cluster_config()
+ if fsid in multi_cluster_config['config']:
+ existing_entries = multi_cluster_config['config'][fsid]
+ if not any(entry['user'] == username for entry in existing_entries):
+ existing_entries.append({
"name": fsid,
"url": url,
"cluster_alias": cluster_alias,
"user": username,
"token": token,
- }]
-
- Settings.MULTICLUSTER_CONFIG = multi_cluster_config
+ })
+ else:
+ multi_cluster_config['current_user'] = username
+ multi_cluster_config['config'][fsid] = [{
+ "name": fsid,
+ "url": url,
+ "cluster_alias": cluster_alias,
+ "user": username,
+ "token": token,
+ }]
+ Settings.MULTICLUSTER_CONFIG = multi_cluster_config
def load_multi_cluster_config(self):
if isinstance(Settings.MULTICLUSTER_CONFIG, str):
Settings.MULTICLUSTER_CONFIG = multicluster_config
return Settings.MULTICLUSTER_CONFIG
- @Endpoint('POST')
+ @Endpoint('PUT')
@CreatePermission
- # pylint: disable=R0911
- def verify_connection(self, url: str, username=None, password=None, token=None):
- if not url.endswith('/'):
- url = url + '/'
+ # pylint: disable=unused-variable
+ def reconnect_cluster(self, url: str, username=None, password=None, token=None):
+ multicluster_config = self.load_multi_cluster_config()
+ if username and password:
+ payload = {
+ 'username': username,
+ 'password': password
+ }
+ content = self._proxy('POST', url, 'api/auth', payload=payload)
+ if 'token' not in content:
+ raise DashboardException(
+ "Could not authenticate to remote cluster",
+ http_status_code=400,
+ component='dashboard')
+ token = content['token']
+
+ if username and token:
+ if "config" in multicluster_config:
+ for key, cluster_details in multicluster_config["config"].items():
+ for cluster in cluster_details:
+ if cluster["url"] == url and cluster["user"] == username:
+ cluster['token'] = token
+ Settings.MULTICLUSTER_CONFIG = multicluster_config
+ return Settings.MULTICLUSTER_CONFIG
+
+ @Endpoint('PUT')
+ @UpdatePermission
+ # pylint: disable=unused-variable
+ def edit_cluster(self, url, cluster_alias, username):
+ multicluster_config = self.load_multi_cluster_config()
+ if "config" in multicluster_config:
+ for key, cluster_details in multicluster_config["config"].items():
+ for cluster in cluster_details:
+ if cluster["url"] == url and cluster["user"] == username:
+ cluster['cluster_alias'] = cluster_alias
+ Settings.MULTICLUSTER_CONFIG = multicluster_config
+ return Settings.MULTICLUSTER_CONFIG
+
+ @Endpoint(method='DELETE')
+ @DeletePermission
+ def delete_cluster(self, cluster_name, cluster_user):
+ multicluster_config = self.load_multi_cluster_config()
+ if "config" in multicluster_config:
+ keys_to_remove = []
+ for key, cluster_details in multicluster_config["config"].items():
+ cluster_details_copy = list(cluster_details)
+ for cluster in cluster_details_copy:
+ if cluster["name"] == cluster_name and cluster["user"] == cluster_user:
+ cluster_details.remove(cluster)
+ if not cluster_details:
+ keys_to_remove.append(key)
+
+ for key in keys_to_remove:
+ del multicluster_config["config"][key]
+
+ Settings.MULTICLUSTER_CONFIG = multicluster_config
+ return Settings.MULTICLUSTER_CONFIG
+
+ @Endpoint()
+ @ReadPermission
+ # pylint: disable=R0911
+ def verify_connection(self, url=None, username=None, password=None, token=None):
if token:
try:
payload = {
def get_config(self):
return Settings.MULTICLUSTER_CONFIG
+ def is_token_expired(self, jwt_token):
+ split_message = jwt_token.split(".")
+ base64_message = split_message[1]
+ decoded_token = json.loads(base64.urlsafe_b64decode(base64_message + "===="))
+ expiration_time = decoded_token['exp']
+ current_time = time.time()
+ return expiration_time < current_time
+
+ def check_token_status_expiration(self, token):
+ if self.is_token_expired(token):
+ return 1
+ return 0
+
+ def check_token_status_array(self, clusters_token_array):
+ token_status_map = {}
+
+ for item in clusters_token_array:
+ cluster_name = item['name']
+ token = item['token']
+ user = item['user']
+ status = self.check_token_status_expiration(token)
+ token_status_map[cluster_name] = {'status': status, 'user': user}
+
+ return token_status_map
+
+ @Endpoint()
+ @ReadPermission
+ def check_token_status(self, clustersTokenMap=None):
+ clusters_token_map = json.loads(clustersTokenMap)
+ return self.check_token_status_array(clusters_token_map)
+
@UIRouter('/multi-cluster', Scope.CONFIG_OPT)
class MultiClusterUi(RESTController):
import { CephfsVolumeFormComponent } from './ceph/cephfs/cephfs-form/cephfs-form.component';
import { UpgradeProgressComponent } from './ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component';
import { MultiClusterComponent } from './ceph/cluster/multi-cluster/multi-cluster.component';
+import { MultiClusterListComponent } from './ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component';
@Injectable()
export class PerformanceCounterBreadcrumbsResolver extends BreadcrumbsResolver {
},
{
path: 'multi-cluster',
- component: MultiClusterComponent
+ children: [
+ {
+ path: 'overview',
+ component: MultiClusterComponent,
+ data: {
+ breadcrumbs: 'Multi-Cluster/Overview'
+ }
+ },
+ {
+ path: 'manage-clusters',
+ component: MultiClusterListComponent,
+ data: {
+ breadcrumbs: 'Multi-Cluster/Manage Clusters'
+ }
+ }
+ ]
},
{
path: 'inventory',
import { UpgradeProgressComponent } from './upgrade/upgrade-progress/upgrade-progress.component';
import { MultiClusterComponent } from './multi-cluster/multi-cluster.component';
import { MultiClusterFormComponent } from './multi-cluster/multi-cluster-form/multi-cluster-form.component';
+import { MultiClusterListComponent } from './multi-cluster/multi-cluster-list/multi-cluster-list.component';
@NgModule({
imports: [
UpgradeStartModalComponent,
UpgradeProgressComponent,
MultiClusterComponent,
- MultiClusterFormComponent
+ MultiClusterFormComponent,
+ MultiClusterListComponent
],
providers: [NgbActiveModal]
})
<cd-modal [modalRef]="activeModal">
<ng-container i18n="form title"
- class="modal-title">Connect Cluster
+ class="modal-title">{{ action | titlecase }} Cluster
</ng-container>
<ng-container class="modal-content">
<form name="remoteClusterForm"
*ngIf="remoteClusterForm.showError('clusterAlias', frm, 'required')"
i18n>This field is required.
</span>
+ <span class="invalid-feedback"
+ *ngIf="remoteClusterForm.showError('clusterAlias', frm, 'uniqueName')"
+ i18n>The chosen alias name is already in use.
+ </span>
</div>
</div>
<div class="form-group row"
- *ngIf="!remoteClusterForm.getValue('showToken') && !showCrossOriginError">
+ *ngIf="action !== 'edit'">
<label class="cd-col-form-label required"
- for="apiToken"
+ for="username"
i18n>Username
</label>
<div class="cd-col-form-input">
*ngIf="remoteClusterForm.showError('username', frm, 'required')"
i18n>This field is required.
</span>
+ <span class="invalid-feedback"
+ *ngIf="remoteClusterForm.showError('username', frm, 'uniqueUrlandUser')"
+ i18n>A cluster with the chosen user is already connected.
+ </span>
+ </div>
+ </div>
+ <div class="form-group row"
+ *ngIf="remoteClusterForm.getValue('showToken') && action !== 'edit'">
+ <label class="cd-col-form-label required"
+ for="clusterFsid"
+ i18n>Cluster FSID
+ </label>
+ <div class="cd-col-form-input">
+ <input id="clusterFsid"
+ name="clusterFsid"
+ class="form-control"
+ type="text"
+ formControlName="clusterFsid">
+ <span class="invalid-feedback"
+ *ngIf="remoteClusterForm.showError('clusterFsid', frm, 'required')"
+ i18n>This field is required.
+ </span>
</div>
</div>
<div class="form-group row"
- *ngIf="!remoteClusterForm.getValue('showToken') && !showCrossOriginError">
+ *ngIf="!remoteClusterForm.getValue('showToken') && !showCrossOriginError && action !== 'edit'">
<label class="cd-col-form-label required"
for="password"
i18n>Password
</span>
</div>
</div>
- <div class="form-group row">
- <div class="cd-col-form-offset">
- <div class="custom-control custom-checkbox">
- <input class="custom-control-input"
- id="showToken"
- type="checkbox"
- (click)="showToken = !showToken"
- formControlName="showToken"
- [readonly]="true">
- <label class="custom-control-label"
- for="showToken"
- i18n>Auth with token</label>
- </div>
- </div>
- </div>
<div class="form-group row"
- *ngIf="remoteClusterForm.getValue('showToken')">
+ *ngIf="remoteClusterForm.getValue('showToken') && action !== 'edit'">
<label class="cd-col-form-label required"
for="apiToken"
i18n>Token
</div>
</div>
<div class="form-group row"
- *ngIf="!showCrossOriginError">
+ *ngIf="action !== 'edit'">
+ <div class="cd-col-form-offset">
+ <div class="custom-control custom-checkbox">
+ <input class="custom-control-input"
+ id="showToken"
+ type="checkbox"
+ [checked]="showToken"
+ (change)="toggleToken()"
+ formControlName="showToken">
+ <label class="custom-control-label"
+ for="showToken"
+ i18n>Auth with token</label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group row"
+ *ngIf="!showCrossOriginError && action !== 'edit' && !remoteClusterForm.getValue('showToken')">
<div class="cd-col-form-offset">
<div class="custom-control">
<button class="btn btn-primary"
</div>
<div class="modal-footer">
<cd-form-button-panel (submitActionEvent)="onSubmit()"
- [submitText]="actionLabels.CONNECT"
- [disabled]="!connectionVerified && !showCrossOriginError"
+ [submitText]="(action | titlecase) + ' ' + 'Cluster'"
[form]="remoteClusterForm">
</cd-form-button-panel>
</div>
-import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import _ from 'lodash';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
import { CdValidators } from '~/app/shared/forms/cd-validators';
+import { MultiCluster } from '~/app/shared/models/multi-cluster';
import { NotificationService } from '~/app/shared/services/notification.service';
@Component({
styleUrls: ['./multi-cluster-form.component.scss']
})
export class MultiClusterFormComponent implements OnInit, OnDestroy {
+ @Output()
+ submitAction = new EventEmitter();
readonly endpoints = /^((https?:\/\/)|(www.))(?:([a-zA-Z]+)|(\d+\.\d+.\d+.\d+)):\d{2,5}\/?$/;
readonly ipv4Rgx = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i;
readonly ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i;
private subs = new Subscription();
showCrossOriginError = false;
crossOriginCmd: string;
+ action: string;
+ cluster: MultiCluster;
+ clustersData: MultiCluster[];
+ clusterAliasNames: string[];
+ clusterUrls: string[];
+ clusterUsers: string[];
+ clusterUrlUserMap: Map<string, string>;
constructor(
public activeModal: NgbActiveModal,
) {
this.createForm();
}
- ngOnInit(): void {}
+ ngOnInit(): void {
+ if (this.action === 'edit') {
+ this.remoteClusterForm.get('remoteClusterUrl').setValue(this.cluster.url);
+ this.remoteClusterForm.get('remoteClusterUrl').disable();
+ this.remoteClusterForm.get('clusterAlias').setValue(this.cluster.cluster_alias);
+ }
+ if (this.action === 'reconnect') {
+ this.remoteClusterForm.get('remoteClusterUrl').setValue(this.cluster.url);
+ this.remoteClusterForm.get('remoteClusterUrl').disable();
+ this.remoteClusterForm.get('clusterAlias').setValue(this.cluster.cluster_alias);
+ this.remoteClusterForm.get('clusterAlias').disable();
+ this.remoteClusterForm.get('username').setValue(this.cluster.user);
+ this.remoteClusterForm.get('username').disable();
+ this.remoteClusterForm.get('clusterFsid').setValue(this.cluster.name);
+ this.remoteClusterForm.get('clusterFsid').disable();
+ }
+ [this.clusterAliasNames, this.clusterUrls, this.clusterUsers] = [
+ 'cluster_alias',
+ 'url',
+ 'user'
+ ].map((prop) => this.clustersData?.map((cluster) => cluster[prop]));
+ }
createForm() {
this.remoteClusterForm = new CdFormGroup({
showToken: new FormControl(false),
username: new FormControl('', [
- CdValidators.requiredIf({
- showToken: false
+ CdValidators.custom('uniqueUrlandUser', (username: string) => {
+ let remoteClusterUrl = '';
+ if (
+ this.remoteClusterForm &&
+ this.remoteClusterForm.getValue('remoteClusterUrl') &&
+ this.remoteClusterForm.getValue('remoteClusterUrl').endsWith('/')
+ ) {
+ remoteClusterUrl = this.remoteClusterForm.getValue('remoteClusterUrl').slice(0, -1);
+ } else if (this.remoteClusterForm) {
+ remoteClusterUrl = this.remoteClusterForm.getValue('remoteClusterUrl');
+ }
+ return (
+ this.remoteClusterForm &&
+ this.clusterUrls?.includes(remoteClusterUrl) &&
+ this.clusterUsers?.includes(username)
+ );
})
]),
- password: new FormControl('', [
+ clusterFsid: new FormControl('', [
CdValidators.requiredIf({
- showToken: false
+ showToken: true
})
]),
+ password: new FormControl('', []),
remoteClusterUrl: new FormControl(null, {
validators: [
CdValidators.custom('endpoint', (value: string) => {
showToken: true
})
]),
- clusterAlias: new FormControl('', {
- validators: [Validators.required]
+ clusterAlias: new FormControl(null, {
+ validators: [
+ Validators.required,
+ CdValidators.custom('uniqueName', (clusterAlias: string) => {
+ return (
+ (this.action === 'connect' || this.action === 'edit') &&
+ this.clusterAliasNames &&
+ this.clusterAliasNames.indexOf(clusterAlias) !== -1
+ );
+ })
+ ]
})
});
}
onSubmit() {
const url = this.remoteClusterForm.getValue('remoteClusterUrl');
+ const updatedUrl = url.endsWith('/') ? url.slice(0, -1) : url;
const clusterAlias = this.remoteClusterForm.getValue('clusterAlias');
const username = this.remoteClusterForm.getValue('username');
const password = this.remoteClusterForm.getValue('password');
const token = this.remoteClusterForm.getValue('apiToken');
+ const clusterFsid = this.remoteClusterForm.getValue('clusterFsid');
- this.subs.add(
- this.multiClusterService
- .addCluster(url, clusterAlias, username, password, token, window.location.origin)
- .subscribe({
+ if (this.action === 'edit') {
+ this.subs.add(
+ this.multiClusterService
+ .editCluster(this.cluster.url, clusterAlias, this.cluster.user)
+ .subscribe({
+ error: () => {
+ this.remoteClusterForm.setErrors({ cdSubmitButton: true });
+ },
+ complete: () => {
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`Cluster updated successfully`
+ );
+ this.submitAction.emit();
+ this.activeModal.close();
+ }
+ })
+ );
+ }
+
+ if (this.action === 'reconnect') {
+ this.subs.add(
+ this.multiClusterService.reConnectCluster(updatedUrl, username, password, token).subscribe({
error: () => {
this.remoteClusterForm.setErrors({ cdSubmitButton: true });
},
complete: () => {
this.notificationService.show(
NotificationType.success,
- $localize`Cluster added successfully`
+ $localize`Cluster reconnected successfully`
);
+ this.submitAction.emit();
this.activeModal.close();
}
})
- );
+ );
+ }
+
+ if (this.action === 'connect') {
+ this.subs.add(
+ this.multiClusterService
+ .addCluster(
+ updatedUrl,
+ clusterAlias,
+ username,
+ password,
+ token,
+ window.location.origin,
+ clusterFsid
+ )
+ .subscribe({
+ error: () => {
+ this.remoteClusterForm.setErrors({ cdSubmitButton: true });
+ },
+ complete: () => {
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`Cluster connected successfully`
+ );
+ this.submitAction.emit();
+ this.activeModal.close();
+ }
+ })
+ );
+ }
}
verifyConnection() {
})
);
}
+
+ toggleToken() {
+ this.showToken = !this.showToken;
+ }
}
--- /dev/null
+<nav ngbNav
+ #nav="ngbNav"
+ class="nav-tabs">
+ <ng-container ngbNavItem>
+ <a ngbNavLink
+ i18n>Clusters List</a>
+ <ng-template ngbNavContent>
+ <cd-table #table
+ [data]="data"
+ [columns]="columns"
+ columnMode="flex"
+ selectionType="single"
+ [maxLimit]="25"
+ (updateSelection)="updateSelection($event)">
+ <div class="table-actions btn-toolbar">
+ <cd-table-actions [permission]="permissions.user"
+ [selection]="selection"
+ class="btn-group"
+ id="cluster-actions"
+ [tableActions]="tableActions">
+ </cd-table-actions>
+ </div>
+ </cd-table>
+ </ng-template>
+ </ng-container>
+</nav>
+
+<ng-template #urlTpl
+ let-row="row">
+ <a target="_blank"
+ [href]="row.url">
+ {{ row.url.endsWith('/') ? row.url.slice(0, -1) : row.url }}
+ <i class="fa fa-external-link"></i>
+ </a>
+</ng-template>
+
+<div [ngbNavOutlet]="nav"></div>
--- /dev/null
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ToastrModule } from 'ngx-toastr';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
+
+import { MultiClusterListComponent } from './multi-cluster-list.component';
+import { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe';
+import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
+import { SharedModule } from '~/app/shared/shared.module';
+
+describe('MultiClusterListComponent', () => {
+ let component: MultiClusterListComponent;
+ let fixture: ComponentFixture<MultiClusterListComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [HttpClientTestingModule, ToastrModule.forRoot(), NgbNavModule, SharedModule],
+ declarations: [MultiClusterListComponent],
+ providers: [CdDatePipe, TableActionsComponent]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(MultiClusterListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component, TemplateRef, ViewChild } from '@angular/core';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { MultiClusterService } from '~/app/shared/api/multi-cluster.service';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { ModalService } from '~/app/shared/services/modal.service';
+import { MultiClusterFormComponent } from '../multi-cluster-form/multi-cluster-form.component';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { Permissions } from '~/app/shared/models/permissions';
+import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
+import { NotificationService } from '~/app/shared/services/notification.service';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
+import { MultiCluster } from '~/app/shared/models/multi-cluster';
+import { SummaryService } from '~/app/shared/services/summary.service';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'cd-multi-cluster-list',
+ templateUrl: './multi-cluster-list.component.html',
+ styleUrls: ['./multi-cluster-list.component.scss']
+})
+export class MultiClusterListComponent {
+ @ViewChild(TableComponent)
+ table: TableComponent;
+ @ViewChild('urlTpl', { static: true })
+ public urlTpl: TemplateRef<any>;
+
+ permissions: Permissions;
+ tableActions: CdTableAction[];
+ clusterTokenStatus: object = {};
+ columns: Array<CdTableColumn> = [];
+ data: any;
+ selection = new CdTableSelection();
+ bsModalRef: NgbModalRef;
+ clustersTokenMap: Map<string, string> = new Map<string, string>();
+ newData: any;
+ modalRef: NgbModalRef;
+
+ constructor(
+ private multiClusterService: MultiClusterService,
+ private router: Router,
+ private summaryService: SummaryService,
+ public actionLabels: ActionLabelsI18n,
+ private notificationService: NotificationService,
+ private authStorageService: AuthStorageService,
+ private modalService: ModalService
+ ) {
+ this.tableActions = [
+ {
+ permission: 'create',
+ icon: Icons.add,
+ name: this.actionLabels.CONNECT,
+ click: () => this.openRemoteClusterInfoModal('connect')
+ },
+ {
+ permission: 'update',
+ icon: Icons.edit,
+ name: this.actionLabels.EDIT,
+ disable: (selection: CdTableSelection) => this.getDisable('edit', selection),
+ click: () => this.openRemoteClusterInfoModal('edit')
+ },
+ {
+ permission: 'update',
+ icon: Icons.refresh,
+ name: this.actionLabels.RECONNECT,
+ disable: (selection: CdTableSelection) => this.getDisable('reconnect', selection),
+ click: () => this.openRemoteClusterInfoModal('reconnect')
+ },
+ {
+ permission: 'delete',
+ icon: Icons.destroy,
+ name: this.actionLabels.DISCONNECT,
+ disable: (selection: CdTableSelection) => this.getDisable('disconnect', selection),
+ click: () => this.openDeleteClusterModal()
+ }
+ ];
+ this.permissions = this.authStorageService.getPermissions();
+ }
+
+ ngOnInit(): void {
+ this.multiClusterService.subscribe((resp: object) => {
+ if (resp && resp['config']) {
+ const clusterDetailsArray = Object.values(resp['config']).flat();
+ this.data = clusterDetailsArray;
+ this.checkClusterConnectionStatus();
+ }
+ });
+
+ this.columns = [
+ {
+ prop: 'cluster_alias',
+ name: $localize`Alias`,
+ flexGrow: 2
+ },
+ {
+ prop: 'cluster_connection_status',
+ name: $localize`Connection`,
+ flexGrow: 2,
+ cellTransformation: CellTemplate.badge,
+ customTemplateConfig: {
+ map: {
+ 1: { value: 'DISCONNECTED', class: 'badge-danger' },
+ 0: { value: 'CONNECTED', class: 'badge-success' },
+ 2: { value: 'CHECKING..', class: 'badge-info' }
+ }
+ }
+ },
+ {
+ prop: 'name',
+ name: $localize`FSID`,
+ flexGrow: 2
+ },
+ {
+ prop: 'url',
+ name: $localize`URL`,
+ flexGrow: 2,
+ cellTemplate: this.urlTpl
+ },
+ {
+ prop: 'user',
+ name: $localize`User`,
+ flexGrow: 2
+ }
+ ];
+
+ this.multiClusterService.subscribeClusterTokenStatus((resp: object) => {
+ this.clusterTokenStatus = resp;
+ this.checkClusterConnectionStatus();
+ });
+ }
+
+ checkClusterConnectionStatus() {
+ if (this.clusterTokenStatus && this.data) {
+ this.data.forEach((cluster: MultiCluster) => {
+ const clusterStatus = this.clusterTokenStatus[cluster.name];
+
+ if (clusterStatus !== undefined) {
+ cluster.cluster_connection_status = clusterStatus.status;
+ } else {
+ cluster.cluster_connection_status = 2;
+ }
+
+ if (cluster.cluster_alias === 'local-cluster') {
+ cluster.cluster_connection_status = 0;
+ }
+ });
+ }
+ }
+
+ openRemoteClusterInfoModal(action: string) {
+ const initialState = {
+ clustersData: this.data,
+ action: action,
+ cluster: this.selection.first()
+ };
+ this.bsModalRef = this.modalService.show(MultiClusterFormComponent, initialState, {
+ size: 'xl'
+ });
+ this.bsModalRef.componentInstance.submitAction.subscribe(() => {
+ this.multiClusterService.refresh();
+ this.summaryService.refresh();
+ const currentRoute = this.router.url.split('?')[0];
+ if (currentRoute.includes('dashboard')) {
+ this.router.navigateByUrl('/pool', { skipLocationChange: true }).then(() => {
+ this.router.navigate([currentRoute]);
+ });
+ } else {
+ this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
+ this.router.navigate([currentRoute]);
+ });
+ }
+ });
+ }
+
+ updateSelection(selection: CdTableSelection) {
+ this.selection = selection;
+ }
+
+ openDeleteClusterModal() {
+ const cluster = this.selection.first();
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ actionDescription: $localize`Disconnect`,
+ itemDescription: $localize`Cluster`,
+ itemNames: [cluster['cluster_alias'] + ' - ' + cluster['user']],
+ submitAction: () =>
+ this.multiClusterService.deleteCluster(cluster['name'], cluster['user']).subscribe(() => {
+ this.modalRef.close();
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`Disconnected cluster '${cluster['cluster_alias']}'`
+ );
+ })
+ });
+ }
+
+ getDisable(action: string, selection: CdTableSelection): string | boolean {
+ if (!selection.hasSelection) {
+ return $localize`Please select one or more clusters to ${action}`;
+ }
+ if (selection.hasSingleSelection) {
+ const cluster = selection.first();
+ if (cluster['cluster_alias'] === 'local-cluster') {
+ return $localize`Cannot ${action} local cluster`;
+ }
+ }
+ return false;
+ }
+}
</span>
<div *ngIf="dashboardClustersMap?.size > 1">
<div *ngIf="!loading">
- <div class="mt-4">
- <div class="text-center">
- <button class="btn btn-primary"
- (click)="openRemoteClusterInfoModal()">
- <i class="mx-auto"
- [ngClass]="icons.add">
- </i> Connect Cluster
- </button>
- </div>
- </div>
</div>
</div>
</div>
}
openRemoteClusterInfoModal() {
- this.bsModalRef = this.modalService.show(MultiClusterFormComponent, {
+ const initialState = {
+ action: 'connect'
+ };
+ this.bsModalRef = this.modalService.show(MultiClusterFormComponent, initialState, {
size: 'xl'
});
}
ngOnInit() {
this.subs.add(this.multiClusterService.startPolling());
+ this.subs.add(this.multiClusterService.startClusterTokenStatusPolling());
this.subs.add(this.summaryService.startPolling());
this.subs.add(this.taskManagerService.init(this.summaryService));
this.faviconService.init();
<div ngbDropdownMenu>
<ng-container *ngFor="let cluster of clustersMap | keyvalue">
<button ngbDropdownItem
- (click)="onClusterSelection(cluster.value)">
+ (click)="onClusterSelection(cluster.value)"
+ [disabled]="cluster.value.cluster_connection_status === 1">
<div class="dropdown-text">{{ cluster.value.name }}</div>
<div *ngIf="cluster.value.cluster_alias"
class="text-secondary">{{ cluster.value.cluster_alias }} - {{ cluster.value.user }}</div>
<li routerLinkActive="active"
class="tc_submenuitem tc_submenuitem_multiCluster_overview">
<a i18n
- routerLink="/multi-cluster">Overview</a>
+ routerLink="/multi-cluster/overview">Overview</a>
+ </li>
+ <li routerLinkActive="active"
+ class="tc_submenuitem tc_submenuitem_multiCluster_manage_clusters">
+ <a i18n
+ routerLink="/multi-cluster/manage-clusters">Manage Clusters</a>
</li>
</ul>
</li>
import { MultiClusterService } from '~/app/shared/api/multi-cluster.service';
import { Icons } from '~/app/shared/enum/icons.enum';
+import { MultiCluster } from '~/app/shared/models/multi-cluster';
import { Permissions } from '~/app/shared/models/permissions';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import {
permissions: Permissions;
enabledFeature$: FeatureTogglesMap$;
+ clusterTokenStatus: object = {};
summaryData: any;
icons = Icons;
ngOnInit() {
this.subs.add(
- this.multiClusterService.subscribe((resp: any) => {
+ this.multiClusterService.subscribe((resp: object) => {
const clustersConfig = resp['config'];
if (clustersConfig) {
Object.keys(clustersConfig).forEach((clusterKey: string) => {
const clusterDetailsList = clustersConfig[clusterKey];
- clusterDetailsList.forEach((clusterDetails: any) => {
- const clusterName = clusterDetails['name'];
+ clusterDetailsList.forEach((clusterDetails: MultiCluster) => {
const clusterUser = clusterDetails['user'];
const clusterUrl = clusterDetails['url'];
const clusterUniqueKey = `${clusterUrl}-${clusterUser}`;
- this.clustersMap.set(clusterUniqueKey, {
- name: clusterName,
- cluster_alias: clusterDetails['cluster_alias'],
- user: clusterDetails['user'],
- url: clusterUrl
- });
+ this.clustersMap.set(clusterUniqueKey, clusterDetails);
+ this.checkClusterConnectionStatus();
});
});
this.selectedCluster =
this.showTopNotification('motdNotificationEnabled', _.isPlainObject(motd));
})
);
+ this.subs.add(
+ this.multiClusterService.subscribeClusterTokenStatus((resp: object) => {
+ this.clusterTokenStatus = resp;
+ this.checkClusterConnectionStatus();
+ })
+ );
}
ngOnDestroy(): void {
this.subs.unsubscribe();
}
+ checkClusterConnectionStatus() {
+ this.clustersMap.forEach((clusterDetails, clusterName) => {
+ const clusterTokenStatus = this.clusterTokenStatus[clusterDetails.name];
+ const connectionStatus = clusterTokenStatus ? clusterTokenStatus.status : 0;
+ const user = clusterTokenStatus ? clusterTokenStatus.user : clusterDetails.user;
+
+ this.clustersMap.set(clusterName, {
+ ...clusterDetails,
+ cluster_connection_status: connectionStatus,
+ user: user
+ });
+
+ if (clusterDetails.cluster_alias === 'local-cluster') {
+ this.clustersMap.set(clusterName, {
+ ...clusterDetails,
+ cluster_connection_status: 0,
+ user: user
+ });
+ }
+ });
+ }
+
blockHealthColor() {
if (this.summaryData && this.summaryData.rbd_mirroring) {
if (this.summaryData.rbd_mirroring.errors > 0) {
-import { HttpClient } from '@angular/common/http';
+import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
-import { BehaviorSubject, Subscription } from 'rxjs';
+import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { TimerService } from '../services/timer.service';
import { filter } from 'rxjs/operators';
providedIn: 'root'
})
export class MultiClusterService {
+ TOKEN_CHECK_INTERVAL = 600000; // 10m interval
private msSource = new BehaviorSubject<any>(null);
msData$ = this.msSource.asObservable();
+ private tokenStatusSource = new BehaviorSubject<any>(null);
+ tokenStatusSource$ = this.tokenStatusSource.asObservable();
constructor(private http: HttpClient, private timerService: TimerService) {}
startPolling(): Subscription {
.subscribe(this.getClusterObserver());
}
+ startClusterTokenStatusPolling() {
+ let clustersTokenMap = new Map<string, { token: string; user: string }>();
+ const dataSubscription = this.subscribe((resp: any) => {
+ const clustersConfig = resp['config'];
+ const tempMap = new Map<string, { token: string; user: string }>();
+ if (clustersConfig) {
+ Object.keys(clustersConfig).forEach((clusterKey: string) => {
+ const clusterDetailsList = clustersConfig[clusterKey];
+ clusterDetailsList.forEach((clusterDetails: any) => {
+ if (clusterDetails['token'] && clusterDetails['name'] && clusterDetails['user']) {
+ tempMap.set(clusterDetails['name'], {
+ token: clusterDetails['token'],
+ user: clusterDetails['user']
+ });
+ }
+ });
+ });
+
+ if (tempMap.size > 0) {
+ clustersTokenMap = tempMap;
+ dataSubscription.unsubscribe();
+ this.checkAndStartTimer(clustersTokenMap);
+ }
+ }
+ });
+ }
+
+ private checkAndStartTimer(clustersTokenMap: Map<string, { token: string; user: string }>) {
+ this.checkTokenStatus(clustersTokenMap).subscribe(this.getClusterTokenStatusObserver());
+ this.timerService
+ .get(() => this.checkTokenStatus(clustersTokenMap), this.TOKEN_CHECK_INTERVAL)
+ .subscribe(this.getClusterTokenStatusObserver());
+ }
+
+ subscribeClusterTokenStatus(next: (data: any) => void, error?: (error: any) => void) {
+ return this.tokenStatusSource$.pipe(filter((value) => !!value)).subscribe(next, error);
+ }
+
refresh(): Subscription {
return this.getCluster().subscribe(this.getClusterObserver());
}
return this.http.get('api/multi-cluster/get_config');
}
+ deleteCluster(clusterName: string, clusterUser: string): Observable<any> {
+ return this.http.delete(`api/multi-cluster/delete_cluster/${clusterName}/${clusterUser}`);
+ }
+
+ editCluster(url: any, clusterAlias: string, username: string) {
+ return this.http.put('api/multi-cluster/edit_cluster', {
+ url,
+ cluster_alias: clusterAlias,
+ username
+ });
+ }
+
addCluster(
url: any,
clusterAlias: string,
username: string,
password: string,
token = '',
- hub_url = ''
+ hub_url = '',
+ clusterFsid = ''
) {
return this.http.post('api/multi-cluster/auth', {
url,
username,
password,
token,
- hub_url
+ hub_url,
+ cluster_fsid: clusterFsid
});
}
- verifyConnection(url: string, username: string, password: string, token = '') {
- return this.http.post('api/multi-cluster/verify_connection', {
+ reConnectCluster(url: any, username: string, password: string, token = '') {
+ return this.http.put('api/multi-cluster/reconnect_cluster', {
url,
username,
password,
});
}
+ verifyConnection(url: string, username: string, password: string, token = ''): Observable<any> {
+ let params = new HttpParams()
+ .set('url', url)
+ .set('username', username)
+ .set('password', password)
+ .set('token', token);
+
+ return this.http.get('api/multi-cluster/verify_connection', { params });
+ }
+
private getClusterObserver() {
return (data: any) => {
this.msSource.next(data);
};
}
+
+ private getClusterTokenStatusObserver() {
+ return (data: any) => {
+ this.tokenStatusSource.next(data);
+ };
+ }
+
+ checkTokenStatus(
+ clustersTokenMap: Map<string, { token: string; user: string }>
+ ): Observable<object> {
+ let data = [...clustersTokenMap].map(([key, { token, user }]) => ({ name: key, token, user }));
+
+ let params = new HttpParams();
+ params = params.set('clustersTokenMap', JSON.stringify(data));
+
+ return this.http.get<object>('api/multi-cluster/check_token_status', { params });
+ }
}
DEACTIVATE: string;
ATTACH: string;
CONNECT: string;
+ DISCONNECT: string;
+ RECONNECT: string;
constructor() {
/* Create a new item */
this.ATTACH = $localize`Attach`;
this.CONNECT = $localize`Connect`;
+ this.DISCONNECT = $localize`Disconnect`;
+ this.RECONNECT = $localize`Reconnect`;
}
}
--- /dev/null
+export interface MultiCluster {
+ name: string;
+ url: string;
+ user: string;
+ token: string;
+ cluster_alias: string;
+ cluster_connection_status: number;
+}
});
}
- const apiUrl = localStorage.getItem('cluster_api_url');
+ let apiUrl = localStorage.getItem('cluster_api_url');
+
+ if (apiUrl && !apiUrl.endsWith('/')) {
+ apiUrl += '/';
+ }
const currentRoute = this.router.url.split('?')[0];
const ALWAYS_TO_HUB_APIs = [
properties:
cluster_alias:
type: string
+ cluster_fsid:
+ type: string
hub_url:
type: string
password:
summary: Authenticate to a remote cluster
tags:
- Multi-cluster
- /api/multi-cluster/get_config:
+ /api/multi-cluster/check_token_status:
get:
- parameters: []
+ parameters:
+ - allowEmptyValue: true
+ in: query
+ name: clustersTokenMap
+ schema:
+ type: string
responses:
'200':
content:
- jwt: []
tags:
- Multi-cluster
- /api/multi-cluster/set_config:
+ /api/multi-cluster/delete_cluster/{cluster_name}/{cluster_user}:
+ delete:
+ parameters:
+ - in: path
+ name: cluster_name
+ required: true
+ schema:
+ type: string
+ - in: path
+ name: cluster_user
+ required: true
+ schema:
+ type: string
+ responses:
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '204':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource deleted.
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - Multi-cluster
+ /api/multi-cluster/edit_cluster:
put:
parameters: []
requestBody:
application/json:
schema:
properties:
- config:
+ cluster_alias:
+ type: string
+ url:
+ type: string
+ username:
type: string
required:
- - config
+ - url
+ - cluster_alias
+ - username
type: object
responses:
'200':
- jwt: []
tags:
- Multi-cluster
- /api/multi-cluster/verify_connection:
- post:
+ /api/multi-cluster/get_config:
+ get:
+ parameters: []
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - Multi-cluster
+ /api/multi-cluster/reconnect_cluster:
+ put:
parameters: []
requestBody:
content:
- url
type: object
responses:
- '201':
+ '200':
content:
application/vnd.ceph.api.v1.0+json:
type: object
- description: Resource created.
+ description: Resource updated.
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - Multi-cluster
+ /api/multi-cluster/set_config:
+ put:
+ parameters: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ config:
+ type: string
+ required:
+ - config
+ type: object
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource updated.
'202':
content:
application/vnd.ceph.api.v1.0+json:
- jwt: []
tags:
- Multi-cluster
+ /api/multi-cluster/verify_connection:
+ get:
+ parameters:
+ - allowEmptyValue: true
+ in: query
+ name: url
+ schema:
+ type: string
+ - allowEmptyValue: true
+ in: query
+ name: username
+ schema:
+ type: string
+ - allowEmptyValue: true
+ in: query
+ name: password
+ schema:
+ type: string
+ - allowEmptyValue: true
+ in: query
+ name: token
+ schema:
+ type: string
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - Multi-cluster
/api/nfs-ganesha/cluster:
get:
parameters: []