Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(xo-web, xo-server/SR): list resource in a XOSTOR tab #7542

Merged
merged 5 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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”
Expand All @@ -27,6 +29,7 @@

<!--packages-start-->

- xo-server patch
- xo-server minor
- xo-web minor

<!--packages-end-->
16 changes: 16 additions & 0 deletions packages/xo-server/src/api/xostor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,19 @@ destroyInterface.params = {
destroyInterface.resolve = {
sr: ['sr', 'SR', 'administrate'],
}
export async function healthCheck({ sr }) {
MathieuRA marked this conversation as resolved.
Show resolved Hide resolved
checkIfLinstorSr(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'],
}
6 changes: 6 additions & 0 deletions packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const messages = {
description: 'Description',
deleteSourceVm: 'Delete source VM',
disable: 'Disable',
diskState: 'Disk state',
download: 'Download',
enable: 'Enable',
expiration: 'Expiration',
Expand All @@ -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',
Expand Down Expand Up @@ -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)…',
Expand Down
13 changes: 13 additions & 0 deletions packages/xo-web/src/common/xo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,19 @@ 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 }), {
polling: 6e4, // To avoid spamming the linstor controller
})
}

return subscribeSrsXostorHealthCheck[srId]
}

// System ============================================================

export const apiMethods = _call('system.getMethodsInfo')
Expand Down
3 changes: 3 additions & 0 deletions packages/xo-web/src/xo-app/sr/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

// ===================================================================

Expand All @@ -33,6 +34,7 @@ import TabXosan from './tab-xosan'
logs: TabLogs,
stats: TabStats,
xosan: TabXosan,
xostor: TabXostor,
})
@connectStore(() => {
const getSr = createGetObject()
Expand Down Expand Up @@ -127,6 +129,7 @@ export default class Sr extends Component {
{sr.SR_type === 'xosan' && <NavLink to={`/srs/${sr.id}/xosan`}>XOSAN</NavLink>}
<NavLink to={`/srs/${sr.id}/hosts`}>{_('hostsTabName')}</NavLink>
<NavLink to={`/srs/${sr.id}/logs`}>{_('logsTabName')}</NavLink>
{sr.SR_type === 'linstor' && <NavLink to={`/srs/${sr.id}/xostor`}>{_('xostor')}</NavLink>}
<NavLink to={`/srs/${sr.id}/advanced`}>{_('advancedTabName')}</NavLink>
</NavTabs>
</Col>
Expand Down
104 changes: 104 additions & 0 deletions packages/xo-web/src/xo-app/sr/tab-xostor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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 }) => <Host id={host.id} link />,
sortCriteria: ({ host }) => host.name_label,
},
{
name: _('nodeStatus'),
itemRenderer: ({ nodeStatus }) => nodeStatus,
sortCriteria: ({ nodeStatus }) => nodeStatus,
},
{
name: _('vdi'),
itemRenderer: ({ vdiId }) => vdiId !== '' && <Vdi id={vdiId} />,
},
{
name: _('inUse'),
itemRenderer: ({ inUse }) => <Icon icon={String(inUse)} />,
sortCriteria: ({ inUse }) => inUse,
},
{
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.keys(healthCheck.resources).flatMap(resourceName => {
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,
host,
resourceName,
})
}
return acc
}, [])
})
}
)

render() {
const resourceInfos = this.getResourceInfos()

return (
<Container>
<Row>
<Col>
<Card>
<CardHeader>
<Icon icon='disk' /> {_('resourceList')}
</CardHeader>
<CardBlock>
<SortedTable collection={resourceInfos} columns={RESOURCE_COLUMNS} stateUrlParam='r' />
</CardBlock>
</Card>
</Col>
</Row>
</Container>
)
}
}