From a3edad733407b6642eff242176efcefa736c9842 Mon Sep 17 00:00:00 2001 From: mathieu Date: Tue, 9 Apr 2024 14:41:11 +0200 Subject: [PATCH 1/4] feat(xo-web/SR): list resource in a xostor tab changelog remove unrelated changes add stateUrlParam --- CHANGELOG.unreleased.md | 3 + packages/xo-server/src/api/xostor.mjs | 15 +++ packages/xo-web/src/common/intl/messages.js | 6 ++ packages/xo-web/src/common/xo/index.js | 11 +++ packages/xo-web/src/xo-app/sr/index.js | 3 + packages/xo-web/src/xo-app/sr/tab-xostor.js | 100 ++++++++++++++++++++ 6 files changed, 138 insertions(+) create mode 100644 packages/xo-web/src/xo-app/sr/tab-xostor.js diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index b302fff0b57..7dc998ae283 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -7,6 +7,8 @@ > Users must be able to say: “Nice enhancement, I'm eager to test it” +- [XOSTOR] List linstor resources in the XOSTOR tab of an SR's view (PR [#7542](https://github.com/vatesfr/xen-orchestra/pull/7542)) + ### Bug fixes > Users must be able to say: “I had this issue, happy to know it's fixed” @@ -28,5 +30,6 @@ - xo-server patch +- xo-web minor diff --git a/packages/xo-server/src/api/xostor.mjs b/packages/xo-server/src/api/xostor.mjs index 2e8b2ecbd32..657b0471c9e 100644 --- a/packages/xo-server/src/api/xostor.mjs +++ b/packages/xo-server/src/api/xostor.mjs @@ -411,3 +411,18 @@ destroyInterface.params = { destroyInterface.resolve = { sr: ['sr', 'SR', 'administrate'], } +export async function healthCheck({ sr }) { + const xapi = this.getXapi(sr) + const pool = this.getObject(sr.$pool) + const groupName = this.getObject(sr.$PBDs[0]).device_config['group-name'] + + return JSON.parse( + await pluginCall(xapi, this.getObject(pool.master), 'linstor-manager', 'healthCheck', { groupName }) + ) +} +healthCheck.params = { + sr: { type: 'string' }, +} +healthCheck.resolve = { + sr: ['sr', 'SR', 'view'], +} diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index d1980ce28e0..dd76416f7af 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -10,6 +10,7 @@ const messages = { description: 'Description', deleteSourceVm: 'Delete source VM', disable: 'Disable', + diskState: 'Disk state', download: 'Download', enable: 'Enable', expiration: 'Expiration', @@ -23,12 +24,16 @@ const messages = { esxiImportStopSourceDescription: 'Source VM stopped before the last delta transfer (after final snapshot). Needed to fully transfer a running VM', esxiImportStopOnErrorDescription: 'Stop on the first error when importing VMs', + inUse: 'In use', nImportVmsInParallel: 'Number of VMs to import in parallel', + node: 'Node', stopOnError: 'Stop on error', uuid: 'UUID', + vdi: 'VDI', vmSrUsage: 'Storage: {used} used of {total} ({free} free)', new: 'New', + nodeStatus: 'Node status', notDefined: 'Not defined', status: 'Status', statusConnecting: 'Connecting', @@ -2593,6 +2598,7 @@ const messages = { pifsNotAttached: 'Not all PIFs are attached', pifsNotStatic: 'Not all PIFs are static', replication: 'Replication', + resourceList: 'Resource list', rpuNoLongerAvailableIfXostor: 'As long as a XOSTOR storage is present in the pool, Rolling Pool Update will not be available', selectDisks: 'Select disk(s)…', diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 02e3e888c9c..2a80aa6d659 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -628,6 +628,17 @@ export const subscribeCloudXoConfig = createSubscription(() => fetch('./rest/v0/cloud/xo-config').then(resp => resp.json()) ) +const subscribeSrsXostorHealthCheck = {} +export const subscribeXostorHealthCheck = sr => { + const srId = resolveId(sr) + + if (subscribeSrsXostorHealthCheck[srId] === undefined) { + subscribeSrsXostorHealthCheck[srId] = createSubscription(() => _call('xostor.healthCheck', { sr: srId })) + } + + return subscribeSrsXostorHealthCheck[srId] +} + // System ============================================================ export const apiMethods = _call('system.getMethodsInfo') diff --git a/packages/xo-web/src/xo-app/sr/index.js b/packages/xo-web/src/xo-app/sr/index.js index 086dd01fe0a..b101641cdda 100644 --- a/packages/xo-web/src/xo-app/sr/index.js +++ b/packages/xo-web/src/xo-app/sr/index.js @@ -22,6 +22,7 @@ import TabHosts from './tab-host' import TabLogs from './tab-logs' import TabStats from './tab-stats' import TabXosan from './tab-xosan' +import TabXostor from './tab-xostor' // =================================================================== @@ -33,6 +34,7 @@ import TabXosan from './tab-xosan' logs: TabLogs, stats: TabStats, xosan: TabXosan, + xostor: TabXostor, }) @connectStore(() => { const getSr = createGetObject() @@ -127,6 +129,7 @@ export default class Sr extends Component { {sr.SR_type === 'xosan' && XOSAN} {_('hostsTabName')} {_('logsTabName')} + {sr.SR_type === 'linstor' && {_('xostor')}} {_('advancedTabName')} diff --git a/packages/xo-web/src/xo-app/sr/tab-xostor.js b/packages/xo-web/src/xo-app/sr/tab-xostor.js new file mode 100644 index 00000000000..17ef5395777 --- /dev/null +++ b/packages/xo-web/src/xo-app/sr/tab-xostor.js @@ -0,0 +1,100 @@ +import _ from 'intl' +import Component from 'base-component' +import Icon from 'icon' +import React from 'react' +import SortedTable from 'sorted-table' + +import { addSubscriptions, connectStore } from 'utils' +import { Card, CardHeader, CardBlock } from 'card' +import { Container, Row, Col } from 'grid' +import { createSelector, createGetObjectsOfType } from 'selectors' +import { Host, Vdi } from 'render-xo-item' +import { subscribeXostorHealthCheck } from 'xo' + +const RESOURCE_COLUMNS = [ + { + name: 'Resource name', + itemRenderer: ({ resourceName }) => resourceName, + sortCriteria: ({ resourceName }) => resourceName, + }, + { + name: _('node'), + itemRenderer: ({ host }) => , + sortCriteria: ({ host }) => host.name_label, + }, + { + name: _('nodeStatus'), + itemRenderer: ({ nodeStatus }) => nodeStatus, + sortCriteria: ({ nodeStatus }) => nodeStatus, + }, + { + name: _('vdi'), + itemRenderer: ({ volume }) => , + }, + { + name: _('inUse'), + itemRenderer: resource => , + sortCriteria: resource => resource['in-use'], + }, + { + name: _('diskState'), + itemRenderer: ({ volume }) => volume['disk-state'], + sortCriteria: ({ volume }) => volume['disk-state'], + }, +] + +@connectStore({ + hostByHostname: createGetObjectsOfType('host') + .filter((_, props) => host => host.$pool === props.sr.$pool) + .groupBy('hostname'), +}) +@addSubscriptions(({ sr }) => ({ + healthCheck: subscribeXostorHealthCheck(sr), +})) +export default class TabXostor extends Component { + getResourceInfos = createSelector( + () => this.props.healthCheck, + healthCheck => { + if (healthCheck === undefined) { + return [] + } + + return Object.entries(healthCheck.resources).flatMap(([resourceName, resourceByHostname]) => { + return Object.entries(resourceByHostname).map(([hostname, resource]) => { + const host = this.props.hostByHostname[hostname][0] + const nodeStatus = healthCheck.nodes[hostname] + const volume = resource.volumes[0] // Always only one volume + + return { + ...resource, + host, + nodeStatus, + resourceName, + volume, + } + }) + }) + } + ) + + render() { + const resourceInfos = this.getResourceInfos() + + return ( + + + + + + {_('resourceList')} + + + + + + + + + ) + } +} From 26510fd973e7b8c71966626c227e1c1e83870d53 Mon Sep 17 00:00:00 2001 From: mathieu Date: Tue, 16 Apr 2024 09:38:58 +0200 Subject: [PATCH 2/4] update to new healthercheck structure --- CHANGELOG.unreleased.md | 2 +- packages/xo-server/src/api/xostor.mjs | 1 + packages/xo-web/src/xo-app/sr/tab-xostor.js | 30 ++++++++++++--------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 7dc998ae283..5d12d1a25ef 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -29,7 +29,7 @@ -- xo-server patch +- xo-server minor - xo-web minor diff --git a/packages/xo-server/src/api/xostor.mjs b/packages/xo-server/src/api/xostor.mjs index 657b0471c9e..b7f4e1a21d0 100644 --- a/packages/xo-server/src/api/xostor.mjs +++ b/packages/xo-server/src/api/xostor.mjs @@ -412,6 +412,7 @@ destroyInterface.resolve = { sr: ['sr', 'SR', 'administrate'], } export async function healthCheck({ sr }) { + checkIfLinstorSr(sr) const xapi = this.getXapi(sr) const pool = this.getObject(sr.$pool) const groupName = this.getObject(sr.$PBDs[0]).device_config['group-name'] diff --git a/packages/xo-web/src/xo-app/sr/tab-xostor.js b/packages/xo-web/src/xo-app/sr/tab-xostor.js index 17ef5395777..9101ff6a565 100644 --- a/packages/xo-web/src/xo-app/sr/tab-xostor.js +++ b/packages/xo-web/src/xo-app/sr/tab-xostor.js @@ -29,7 +29,7 @@ const RESOURCE_COLUMNS = [ }, { name: _('vdi'), - itemRenderer: ({ volume }) => , + itemRenderer: ({ vdiId }) => , }, { name: _('inUse'), @@ -59,20 +59,24 @@ export default class TabXostor extends Component { return [] } - return Object.entries(healthCheck.resources).flatMap(([resourceName, resourceByHostname]) => { - return Object.entries(resourceByHostname).map(([hostname, resource]) => { - const host = this.props.hostByHostname[hostname][0] - const nodeStatus = healthCheck.nodes[hostname] - const volume = resource.volumes[0] // Always only one volume + return Object.keys(healthCheck.resources).flatMap(resourceName => { + return Object.keys(healthCheck.resources[resourceName].nodes).reduce((acc, hostname) => { + const nodeInfo = healthCheck.resources[resourceName].nodes[hostname] + const volume = nodeInfo.volumes[0] // Max only one volume + if (volume !== undefined) { + const nodeStatus = healthCheck.nodes[hostname] + const host = this.props.hostByHostname[hostname][0] - return { - ...resource, - host, - nodeStatus, - resourceName, - volume, + acc.push({ + vdiId: healthCheck.resources[resourceName].uuid, + volume, + nodeStatus, + host, + resourceName, + }) } - }) + return acc + }, []) }) } ) From 1c4404b4f1f5dc8e38a2c2758fc6ad879bfca737 Mon Sep 17 00:00:00 2001 From: mathieu Date: Wed, 24 Apr 2024 15:36:18 +0200 Subject: [PATCH 3/4] fix in-use path --- packages/xo-web/src/xo-app/sr/tab-xostor.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/xo-web/src/xo-app/sr/tab-xostor.js b/packages/xo-web/src/xo-app/sr/tab-xostor.js index 9101ff6a565..b859669b158 100644 --- a/packages/xo-web/src/xo-app/sr/tab-xostor.js +++ b/packages/xo-web/src/xo-app/sr/tab-xostor.js @@ -29,12 +29,12 @@ const RESOURCE_COLUMNS = [ }, { name: _('vdi'), - itemRenderer: ({ vdiId }) => , + itemRenderer: ({ vdiId }) => vdiId !== '' && , }, { name: _('inUse'), - itemRenderer: resource => , - sortCriteria: resource => resource['in-use'], + itemRenderer: ({ inUse }) => , + sortCriteria: ({ inUse }) => inUse, }, { name: _('diskState'), @@ -60,14 +60,14 @@ export default class TabXostor extends Component { } return Object.keys(healthCheck.resources).flatMap(resourceName => { - return Object.keys(healthCheck.resources[resourceName].nodes).reduce((acc, hostname) => { - const nodeInfo = healthCheck.resources[resourceName].nodes[hostname] + return Object.entries(healthCheck.resources[resourceName].nodes).reduce((acc, [hostname, nodeInfo]) => { const volume = nodeInfo.volumes[0] // Max only one volume if (volume !== undefined) { const nodeStatus = healthCheck.nodes[hostname] const host = this.props.hostByHostname[hostname][0] acc.push({ + inUse: nodeInfo['in-use'], vdiId: healthCheck.resources[resourceName].uuid, volume, nodeStatus, From e0569b453fb40a8649db7f6704f2ca0181a0e744 Mon Sep 17 00:00:00 2001 From: mathieu Date: Wed, 24 Apr 2024 18:37:55 +0200 Subject: [PATCH 4/4] polling every minute --- packages/xo-web/src/common/xo/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 2a80aa6d659..49c87c9b05c 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -633,7 +633,9 @@ export const subscribeXostorHealthCheck = sr => { const srId = resolveId(sr) if (subscribeSrsXostorHealthCheck[srId] === undefined) { - subscribeSrsXostorHealthCheck[srId] = createSubscription(() => _call('xostor.healthCheck', { sr: srId })) + subscribeSrsXostorHealthCheck[srId] = createSubscription(() => _call('xostor.healthCheck', { sr: srId }), { + polling: 6e4, // To avoid spamming the linstor controller + }) } return subscribeSrsXostorHealthCheck[srId]