feat(proxy): ability to set the deployed VM network configuration (#4810)
This commit is contained in:
parent
692d5be166
commit
18685d061a
@ -9,6 +9,7 @@
|
||||
|
||||
- [New VM] Ability to set VM max vCPUS [#4703](https://github.com/vatesfr/xen-orchestra/issues/4703) (PR [#4729](https://github.com/vatesfr/xen-orchestra/pull/4729))
|
||||
- [SDN Controller] Automatically handle new, connected and disconnected servers (PR [#4677](https://github.com/vatesfr/xen-orchestra/pull/4677))
|
||||
- [Proxy] Support network configuration for the deployed proxy (PR [#4810](https://github.com/vatesfr/xen-orchestra/pull/4810))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
@ -137,7 +137,7 @@ proxyName = 'Proxy {date}'
|
||||
vmName = 'XOA Proxy {date}'
|
||||
|
||||
# The duration for which we can wait for the VM networks to be defined
|
||||
vmNetworksTimeout = '1 min'
|
||||
vmNetworksTimeout = '5 min'
|
||||
|
||||
vmTag = 'XOA Proxy'
|
||||
|
||||
|
@ -96,8 +96,8 @@ update.params = {
|
||||
},
|
||||
}
|
||||
|
||||
export function deploy({ sr }) {
|
||||
return this.deployProxy(sr._xapiId)
|
||||
export function deploy({ network, sr }) {
|
||||
return this.deployProxy(sr._xapiId, { network })
|
||||
}
|
||||
|
||||
deploy.permission = 'admin'
|
||||
@ -105,6 +105,24 @@ deploy.params = {
|
||||
sr: {
|
||||
type: 'string',
|
||||
},
|
||||
network: {
|
||||
type: 'object',
|
||||
optional: true,
|
||||
properties: {
|
||||
ip: {
|
||||
type: 'string',
|
||||
},
|
||||
netmask: {
|
||||
type: 'string',
|
||||
},
|
||||
gateway: {
|
||||
type: 'string',
|
||||
},
|
||||
dns: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
deploy.resolve = {
|
||||
sr: ['sr', 'SR', 'administrate'],
|
||||
|
@ -8,7 +8,7 @@ import { compileTemplate } from '@xen-orchestra/template'
|
||||
import { format, parse } from 'json-rpc-peer'
|
||||
import { noSuchObject } from 'xo-common/api-errors'
|
||||
import { NULL_REF } from 'xen-api'
|
||||
import { omit } from 'lodash'
|
||||
import { mapValues, omit } from 'lodash'
|
||||
import { timeout } from 'promise-toolbox'
|
||||
|
||||
import Collection from '../collection/redis'
|
||||
@ -133,7 +133,7 @@ export default class Proxy {
|
||||
}
|
||||
|
||||
@defer
|
||||
async deployProxy($defer, srId) {
|
||||
async deployProxy($defer, srId, { network } = {}) {
|
||||
const app = this._app
|
||||
const xoProxyConf = this._xoProxyConf
|
||||
|
||||
@ -162,29 +162,34 @@ export default class Proxy {
|
||||
app.getApplianceRegistration(),
|
||||
])
|
||||
const date = new Date()
|
||||
const xenstoreData = {
|
||||
'vm-data/system-account-xoa-password': password,
|
||||
'vm-data/xo-proxy-authenticationToken': JSON.stringify(
|
||||
proxyAuthenticationToken
|
||||
),
|
||||
'vm-data/xoa-updater-credentials': JSON.stringify({
|
||||
email,
|
||||
registrationToken,
|
||||
}),
|
||||
'vm-data/xoa-updater-channel': JSON.stringify(xoProxyConf.channel),
|
||||
}
|
||||
if (network !== undefined) {
|
||||
xenstoreData['vm-data/ip'] = network.ip
|
||||
xenstoreData['vm-data/gateway'] = network.gateway
|
||||
xenstoreData['vm-data/netmask'] = network.netmask
|
||||
xenstoreData['vm-data/dns'] = network.dns
|
||||
}
|
||||
await Promise.all([
|
||||
vm.add_tags(xoProxyConf.vmTag),
|
||||
vm.set_name_label(this._generateDefaultVmName(date)),
|
||||
vm.update_xenstore_data({
|
||||
'vm-data/system-account-xoa-password': password,
|
||||
'vm-data/xo-proxy-authenticationToken': JSON.stringify(
|
||||
proxyAuthenticationToken
|
||||
),
|
||||
'vm-data/xoa-updater-credentials': JSON.stringify({
|
||||
email,
|
||||
registrationToken,
|
||||
}),
|
||||
'vm-data/xoa-updater-channel': JSON.stringify(xoProxyConf.channel),
|
||||
}),
|
||||
vm.update_xenstore_data(xenstoreData),
|
||||
])
|
||||
|
||||
await xapi.startVm(vm.$id)
|
||||
|
||||
await vm.update_xenstore_data({
|
||||
'vm-data/system-account-xoa-password': null,
|
||||
'vm-data/xo-proxy-authenticationToken': null,
|
||||
'vm-data/xoa-updater-credentials': null,
|
||||
})
|
||||
await vm.update_xenstore_data(
|
||||
mapValues(omit(xenstoreData, 'vm-data/xoa-updater-channel'), _ => null)
|
||||
)
|
||||
|
||||
// ensure appliance has an IP address
|
||||
const vmNetworksTimeout = parseDuration(xoProxyConf.vmNetworksTimeout)
|
||||
@ -193,9 +198,10 @@ export default class Proxy {
|
||||
vmNetworksTimeout
|
||||
)
|
||||
await timeout.call(
|
||||
xapi._waitObjectState(
|
||||
vm.guest_metrics,
|
||||
guest_metrics => guest_metrics.networks['0/ip'] !== undefined
|
||||
xapi._waitObjectState(vm.guest_metrics, guest_metrics =>
|
||||
network === undefined
|
||||
? guest_metrics.networks['0/ip'] !== undefined
|
||||
: guest_metrics.networks['0/ip'] === network.ip
|
||||
),
|
||||
vmNetworksTimeout
|
||||
)
|
||||
|
@ -59,6 +59,10 @@ const messages = {
|
||||
name: 'Name',
|
||||
address: 'Address',
|
||||
vm: 'VM',
|
||||
destinationSR: 'Destination SR',
|
||||
dhcp: 'DHCP',
|
||||
ip: 'IP',
|
||||
static: 'Static',
|
||||
|
||||
// ----- Modals -----
|
||||
alertOk: 'OK',
|
||||
@ -2271,6 +2275,8 @@ const messages = {
|
||||
proxyTestSuccessMessage: 'The proxy appears to work correctly',
|
||||
proxyLinkedRemotes: 'Click to see linked remotes',
|
||||
proxyLinkedBackups: 'Click to see linked backups',
|
||||
proxyNetworkDnsPlaceHolder: 'Default to: {dns}',
|
||||
proxyNetworkNetmaskPlaceHolder: 'Default to: {netmask}',
|
||||
|
||||
// ----- Utils -----
|
||||
secondsFormat: '{seconds, plural, one {# second} other {# seconds}}',
|
||||
|
@ -3061,8 +3061,8 @@ export const getApplianceInfo = () => _call('xoa.getApplianceInfo')
|
||||
|
||||
// Proxy --------------------------------------------------------------------
|
||||
|
||||
export const deployProxyAppliance = sr =>
|
||||
_call('proxy.deploy', { sr: resolveId(sr) })::tap(
|
||||
export const deployProxyAppliance = (sr, props) =>
|
||||
_call('proxy.deploy', { sr: resolveId(sr), ...props })::tap(
|
||||
subscribeProxies.forceRefresh
|
||||
)
|
||||
|
||||
|
212
packages/xo-web/src/xo-app/proxies/deploy-proxy.js
Normal file
212
packages/xo-web/src/xo-app/proxies/deploy-proxy.js
Normal file
@ -0,0 +1,212 @@
|
||||
import _, { messages } from 'intl'
|
||||
import decorate from 'apply-decorators'
|
||||
import Icon from 'icon'
|
||||
import React from 'react'
|
||||
import SingleLineRow from 'single-line-row'
|
||||
import { Col, Container } from 'grid'
|
||||
import { deployProxyAppliance } from 'xo'
|
||||
import { form } from 'modal'
|
||||
import { generateId } from 'reaclette-utils'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { provideState, injectState } from 'reaclette'
|
||||
import { Select } from 'form'
|
||||
import { SelectSr } from 'select-objects'
|
||||
|
||||
const Label = ({ children, ...props }) => (
|
||||
<label {...props} style={{ cursor: 'pointer' }}>
|
||||
<strong>{children}</strong>
|
||||
</label>
|
||||
)
|
||||
|
||||
const NETWORK_MODE_OPTIONS = [
|
||||
{
|
||||
label: _('dhcp'),
|
||||
value: 'dhcp',
|
||||
},
|
||||
{
|
||||
label: _('static'),
|
||||
value: 'static',
|
||||
},
|
||||
]
|
||||
|
||||
const DEFAULT_DNS = '8.8.8.8'
|
||||
const DEFAULT_NETMASK = '255.255.255.0'
|
||||
|
||||
const Modal = decorate([
|
||||
provideState({
|
||||
effects: {
|
||||
onSrChange(_, sr) {
|
||||
this.props.onChange({
|
||||
...this.props.value,
|
||||
sr,
|
||||
})
|
||||
},
|
||||
onNetworkModeChange(_, networkMode) {
|
||||
this.props.onChange({
|
||||
...this.props.value,
|
||||
networkMode,
|
||||
})
|
||||
},
|
||||
onInputChange(_, { target: { name, value } }) {
|
||||
this.props.onChange({
|
||||
...this.props.value,
|
||||
[name]: value,
|
||||
})
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
idDnsInput: generateId,
|
||||
idGatewayInput: generateId,
|
||||
idIpInput: generateId,
|
||||
idNetmaskInput: generateId,
|
||||
idSelectNetworkMode: generateId,
|
||||
idSelectSr: generateId,
|
||||
|
||||
isStaticMode: (state, { value }) => value.networkMode === 'static',
|
||||
},
|
||||
}),
|
||||
injectState,
|
||||
injectIntl,
|
||||
({ effects, state, value, intl: { formatMessage } }) => (
|
||||
<Container>
|
||||
<SingleLineRow>
|
||||
<Col mediumSize={4}>
|
||||
<Label htmlFor={state.idSelectSr}>{_('destinationSR')}</Label>
|
||||
</Col>
|
||||
<Col mediumSize={8}>
|
||||
<SelectSr
|
||||
id={state.idSelectSr}
|
||||
onChange={effects.onSrChange}
|
||||
required
|
||||
value={value.sr}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
<SingleLineRow className='mt-1'>
|
||||
<Col mediumSize={4}>
|
||||
<Label htmlFor={state.idSelectNetworkMode}>{_('network')}</Label>
|
||||
</Col>
|
||||
<Col mediumSize={8}>
|
||||
<Select
|
||||
id={state.idSelectNetworkMode}
|
||||
onChange={effects.onNetworkModeChange}
|
||||
options={NETWORK_MODE_OPTIONS}
|
||||
required
|
||||
simpleValue
|
||||
value={value.networkMode}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
{state.isStaticMode && (
|
||||
<div>
|
||||
<SingleLineRow className='mt-1'>
|
||||
<Col mediumSize={4}>
|
||||
<Label htmlFor={state.idIpInput}>{_('ip')}</Label>
|
||||
</Col>
|
||||
<Col mediumSize={8}>
|
||||
<input
|
||||
className='form-control'
|
||||
id={state.idIpInput}
|
||||
name='ip'
|
||||
onChange={effects.onInputChange}
|
||||
pattern='[^\s]+'
|
||||
required={state.isStaticMode}
|
||||
value={value.ip}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
<SingleLineRow className='mt-1'>
|
||||
<Col mediumSize={4}>
|
||||
<Label htmlFor={state.idNetmaskInput}>{_('netmask')}</Label>
|
||||
</Col>
|
||||
<Col mediumSize={8}>
|
||||
<input
|
||||
className='form-control'
|
||||
id={state.idNetmaskInput}
|
||||
name='netmask'
|
||||
onChange={effects.onInputChange}
|
||||
placeholder={formatMessage(
|
||||
messages.proxyNetworkNetmaskPlaceHolder,
|
||||
{
|
||||
netmask: DEFAULT_NETMASK,
|
||||
}
|
||||
)}
|
||||
value={value.netmask}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
<SingleLineRow className='mt-1'>
|
||||
<Col mediumSize={4}>
|
||||
<Label htmlFor={state.idGatewayInput}>{_('gateway')}</Label>
|
||||
</Col>
|
||||
<Col mediumSize={8}>
|
||||
<input
|
||||
className='form-control'
|
||||
id={state.idGatewayInput}
|
||||
name='gateway'
|
||||
onChange={effects.onInputChange}
|
||||
pattern='[^\s]+'
|
||||
required={state.isStaticMode}
|
||||
value={value.gateway}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
<SingleLineRow className='mt-1'>
|
||||
<Col mediumSize={4}>
|
||||
<Label htmlFor={state.idDnsInput}>{_('dns')}</Label>
|
||||
</Col>
|
||||
<Col mediumSize={8}>
|
||||
<input
|
||||
className='form-control'
|
||||
id={state.idDnsInput}
|
||||
name='dns'
|
||||
onChange={effects.onInputChange}
|
||||
placeholder={formatMessage(
|
||||
messages.proxyNetworkDnsPlaceHolder,
|
||||
{
|
||||
dns: DEFAULT_DNS,
|
||||
}
|
||||
)}
|
||||
value={value.dns}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
),
|
||||
])
|
||||
|
||||
const deployProxy = () =>
|
||||
form({
|
||||
defaultValue: {
|
||||
dns: '',
|
||||
gateway: '',
|
||||
ip: '',
|
||||
netmask: '',
|
||||
networkMode: 'dhcp',
|
||||
},
|
||||
render: props => <Modal {...props} />,
|
||||
header: (
|
||||
<span>
|
||||
<Icon icon='proxy' /> {_('deployProxy')}
|
||||
</span>
|
||||
),
|
||||
}).then(({ sr, networkMode, ip, netmask, gateway, dns }) =>
|
||||
deployProxyAppliance(
|
||||
sr,
|
||||
networkMode === 'static'
|
||||
? {
|
||||
network: {
|
||||
dns: (dns = dns.trim()) === '' ? DEFAULT_DNS : dns,
|
||||
gateway,
|
||||
ip,
|
||||
netmask:
|
||||
(netmask = netmask.trim()) === '' ? DEFAULT_NETMASK : netmask,
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
)
|
||||
|
||||
export { deployProxy as default }
|
@ -8,14 +8,11 @@ import NoObjects from 'no-objects'
|
||||
import React from 'react'
|
||||
import SortedTable from 'sorted-table'
|
||||
import { adminOnly } from 'utils'
|
||||
import { form } from 'modal'
|
||||
import { SelectSr } from 'select-objects'
|
||||
import { Text, XoSelect } from 'editable'
|
||||
import { Vm } from 'render-xo-item'
|
||||
import { withRouter } from 'react-router'
|
||||
import {
|
||||
checkProxyHealth,
|
||||
deployProxyAppliance,
|
||||
destroyProxyAppliances,
|
||||
forgetProxyAppliances,
|
||||
editProxyAppliance,
|
||||
@ -25,17 +22,7 @@ import {
|
||||
|
||||
import Page from '../page'
|
||||
|
||||
const _deployProxy = () =>
|
||||
form({
|
||||
render: ({ onChange, value }) => (
|
||||
<SelectSr onChange={onChange} value={value} required />
|
||||
),
|
||||
header: (
|
||||
<span>
|
||||
<Icon icon='proxy' /> {_('deployProxy')}
|
||||
</span>
|
||||
),
|
||||
}).then(deployProxyAppliance)
|
||||
import deployProxy from './deploy-proxy'
|
||||
|
||||
const _editProxy = (value, { name, proxy }) =>
|
||||
editProxyAppliance(proxy, { [name]: value })
|
||||
@ -172,7 +159,7 @@ export default decorate([
|
||||
<div className='mt-1 mb-1'>
|
||||
<ActionButton
|
||||
btnStyle='success'
|
||||
handler={_deployProxy}
|
||||
handler={deployProxy}
|
||||
icon='proxy'
|
||||
size='large'
|
||||
>
|
||||
|
Loading…
Reference in New Issue
Block a user