feat(sdn-controller/new network): choose preferred center (#5000)
Fixes #4991
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
- [OVA import] Add support for OVA 2.0 file format (PR [#4921](https://github.com/vatesfr/xen-orchestra/pull/4921))
|
||||
- [Audit] Record failed connection attempts [#4844](https://github.com/vatesfr/xen-orchestra/issues/4844) (PR [#4900](https://github.com/vatesfr/xen-orchestra/pull/4900))
|
||||
- [XO config export] Ability to encrypt the exported file (PR [#4997](https://github.com/vatesfr/xen-orchestra/pull/4997))
|
||||
- [SDN Controller] Ability to choose host as preferred center at private network creation [#4991](https://github.com/vatesfr/xen-orchestra/issues/4991) (PR [#5000](https://github.com/vatesfr/xen-orchestra/pull/5000))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
||||
@@ -236,8 +236,16 @@ async function createTunnel(host, network) {
|
||||
}
|
||||
|
||||
try {
|
||||
await host.$xapi.call('tunnel.create', hostPif.$ref, network.$ref)
|
||||
await host.$xapi.barrier(host.$ref)
|
||||
const tunnelRef = await host.$xapi.call(
|
||||
'tunnel.create',
|
||||
hostPif.$ref,
|
||||
network.$ref
|
||||
)
|
||||
const tunnel = await host.$xapi._getOrWaitObject(tunnelRef)
|
||||
await tunnel.$xapi._waitObjectState(
|
||||
tunnel.access_PIF,
|
||||
pif => pif.currently_attached
|
||||
)
|
||||
} catch (error) {
|
||||
log.error('Error while creating tunnel', {
|
||||
error,
|
||||
@@ -293,6 +301,7 @@ class SDNController extends EventEmitter {
|
||||
- `xo:sdn-controller:encapsulation` : encapsulation protocol used for tunneling (either `gre` or `vxlan`)
|
||||
- `xo:sdn-controller:encrypted` : `true` if the network is encrypted
|
||||
- `xo:sdn-controller:pif-device` : PIF device on which the tunnels are created, must be physical or VLAN or bond master and have an IP configuration
|
||||
- `xo:sdn-controller:preferred-center` : The host UUID to prioritize as network center (or not defined)
|
||||
- `xo:sdn-controller:private-network-uuid`: UUID of the private network, same across pools
|
||||
- `xo:sdn-controller:vlan` : VLAN of the PIFs on which the network is created
|
||||
- `xo:sdn-controller:vni` : VxLAN Network Identifier,
|
||||
@@ -410,6 +419,7 @@ class SDNController extends EventEmitter {
|
||||
encapsulation: { type: 'string' },
|
||||
encrypted: { type: 'boolean', optional: true },
|
||||
mtu: { type: 'integer', optional: true },
|
||||
preferredCenterId: { type: 'string', optional: true },
|
||||
}
|
||||
|
||||
this._unsetApiMethods = this._xo.addApiMethods({
|
||||
@@ -500,10 +510,19 @@ class SDNController extends EventEmitter {
|
||||
return
|
||||
}
|
||||
|
||||
const privateNetwork =
|
||||
this.privateNetworks[uuid] !== undefined
|
||||
? this.privateNetworks[uuid]
|
||||
: new PrivateNetwork(this, uuid)
|
||||
let privateNetwork = this.privateNetworks[uuid]
|
||||
if (privateNetwork === undefined) {
|
||||
const preferredCenterUuid =
|
||||
otherConfig['xo:sdn-controller:preferred-center']
|
||||
const preferredCenter =
|
||||
preferredCenterUuid !== undefined
|
||||
? this._xo.getXapiObject(
|
||||
this._xo.getObject(preferredCenterUuid, 'host')
|
||||
)
|
||||
: undefined
|
||||
privateNetwork = new PrivateNetwork(this, uuid, preferredCenter)
|
||||
this.privateNetworks[uuid] = privateNetwork
|
||||
}
|
||||
|
||||
const vni = otherConfig['xo:sdn-controller:vni']
|
||||
if (vni === undefined) {
|
||||
@@ -622,8 +641,22 @@ class SDNController extends EventEmitter {
|
||||
vni,
|
||||
encrypted,
|
||||
mtu,
|
||||
preferredCenterId,
|
||||
}) {
|
||||
const privateNetwork = new PrivateNetwork(this, uuidv4())
|
||||
let preferredCenter
|
||||
if (preferredCenterId !== undefined) {
|
||||
preferredCenter = this._xo.getXapiObject(
|
||||
this._xo.getObject(preferredCenterId, 'host')
|
||||
)
|
||||
|
||||
// Put pool of preferred center first
|
||||
const i = poolIds.indexOf(preferredCenterId)
|
||||
assert.notStrictEqual(i, -1)
|
||||
poolIds[i] = poolIds[0]
|
||||
poolIds[0] = preferredCenterId
|
||||
}
|
||||
|
||||
const privateNetwork = new PrivateNetwork(this, uuidv4(), preferredCenter)
|
||||
for (const poolId of poolIds) {
|
||||
const pool = this._xo.getXapiObject(this._xo.getObject(poolId, 'pool'))
|
||||
|
||||
@@ -647,6 +680,7 @@ class SDNController extends EventEmitter {
|
||||
'xo:sdn-controller:encapsulation': encapsulation,
|
||||
'xo:sdn-controller:encrypted': encrypted ? 'true' : undefined,
|
||||
'xo:sdn-controller:pif-device': pif.device,
|
||||
'xo:sdn-controller:preferred-center': preferredCenter?.uuid,
|
||||
'xo:sdn-controller:private-network-uuid': privateNetwork.uuid,
|
||||
'xo:sdn-controller:vlan': String(pif.VLAN),
|
||||
'xo:sdn-controller:vni': String(vni),
|
||||
|
||||
@@ -14,7 +14,9 @@ const createPassword = () =>
|
||||
// =============================================================================
|
||||
|
||||
export class PrivateNetwork {
|
||||
constructor(controller, uuid) {
|
||||
constructor(controller, uuid, preferredCenter) {
|
||||
this._preferredCenter = preferredCenter
|
||||
|
||||
this.controller = controller
|
||||
this.uuid = uuid
|
||||
this.networks = {}
|
||||
@@ -125,18 +127,13 @@ export class PrivateNetwork {
|
||||
async electNewCenter() {
|
||||
delete this.center
|
||||
|
||||
// TODO: make it random
|
||||
const hosts = this._getHosts()
|
||||
for (const host of hosts) {
|
||||
const pif = host.$PIFs.find(
|
||||
_ => _.network === this.networks[host.$pool.uuid].$ref
|
||||
if (this._preferredCenter !== undefined) {
|
||||
this._preferredCenter = await this._preferredCenter.$xapi.barrier(
|
||||
this._preferredCenter.$ref
|
||||
)
|
||||
if (pif?.currently_attached && host.$metrics.live) {
|
||||
this.center = host
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.center = this._findBestCenter()
|
||||
if (this.center === undefined) {
|
||||
log.error('No available host to elect new star-center', {
|
||||
privateNetwork: this.uuid,
|
||||
@@ -147,8 +144,7 @@ export class PrivateNetwork {
|
||||
await this._reset()
|
||||
|
||||
// Recreate star topology
|
||||
await Promise.all(hosts.map(host => this.addHost(host)))
|
||||
|
||||
await Promise.all(this._getHosts().map(host => this.addHost(host)))
|
||||
log.info('New star-center elected', {
|
||||
center: this.center.name_label,
|
||||
privateNetwork: this.uuid,
|
||||
@@ -201,4 +197,27 @@ export class PrivateNetwork {
|
||||
})
|
||||
return hosts
|
||||
}
|
||||
|
||||
_hostCanBeCenter(host) {
|
||||
const pif = host.$PIFs.find(
|
||||
_ => _.network === this.networks[host.$pool.uuid].$ref
|
||||
)
|
||||
return pif?.currently_attached && host.$metrics.live
|
||||
}
|
||||
|
||||
_findBestCenter() {
|
||||
if (
|
||||
this._preferredCenter !== undefined &&
|
||||
this._hostCanBeCenter(this._preferredCenter)
|
||||
) {
|
||||
return this._preferredCenter
|
||||
}
|
||||
|
||||
// TODO: make it random
|
||||
for (const host of this._getHosts()) {
|
||||
if (this._hostCanBeCenter(host)) {
|
||||
return host
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1830,10 +1830,12 @@ const messages = {
|
||||
newNetworkBondMode: 'Bond mode',
|
||||
newNetworkInfo: 'Info',
|
||||
newNetworkType: 'Type',
|
||||
newNetworkPreferredCenter: 'Preferred center (optional)',
|
||||
newNetworkEncapsulation: 'Encapsulation',
|
||||
newNetworkEncrypted: 'Encrypted',
|
||||
encryptionWarning:
|
||||
'A pool can have 1 encrypted GRE network and 1 encrypted VxLAN network max',
|
||||
preferredCenterTip: 'The host to try first to elect as center of the network',
|
||||
newNetworkSdnControllerTip: 'Please see the requirements',
|
||||
deleteNetwork: 'Delete network',
|
||||
deleteNetworkConfirm: 'Are you sure you want to delete this network?',
|
||||
|
||||
@@ -1799,7 +1799,13 @@ export const createNetwork = params => _call('network.create', params)
|
||||
export const createBondedNetwork = params =>
|
||||
_call('network.createBonded', params)
|
||||
export const createPrivateNetwork = params =>
|
||||
_call('sdnController.createPrivateNetwork', params)
|
||||
_call('sdnController.createPrivateNetwork', {
|
||||
...params,
|
||||
preferredCenterId:
|
||||
params.preferredCenter !== null
|
||||
? resolveId(params.preferredCenter)
|
||||
: undefined,
|
||||
})
|
||||
|
||||
export const deleteNetwork = network =>
|
||||
confirm({
|
||||
|
||||
@@ -25,7 +25,7 @@ import { injectState, provideState } from 'reaclette'
|
||||
import { linkState } from 'reaclette-utils'
|
||||
import { map } from 'lodash'
|
||||
import { Select, Toggle } from 'form'
|
||||
import { SelectPif, SelectPool } from 'select-objects'
|
||||
import { SelectHost, SelectPif, SelectPool } from 'select-objects'
|
||||
|
||||
import Page from '../../page'
|
||||
import styles from './index.css'
|
||||
@@ -33,6 +33,7 @@ import styles from './index.css'
|
||||
const EMPTY = {
|
||||
bonded: false,
|
||||
bondMode: undefined,
|
||||
networkCenter: undefined,
|
||||
description: '',
|
||||
encapsulation: 'gre',
|
||||
encrypted: false,
|
||||
@@ -117,6 +118,9 @@ const NewNetwork = decorate([
|
||||
onChangeEncapsulation(_, encapsulation) {
|
||||
return { encapsulation: encapsulation.value }
|
||||
},
|
||||
onChangeCenter(_, networkCenter) {
|
||||
this.state.networkCenter = networkCenter
|
||||
},
|
||||
onDeletePool(_, { currentTarget: { dataset } }) {
|
||||
const networks = [...this.state.networks]
|
||||
networks.splice(dataset.position, 1)
|
||||
@@ -153,6 +157,11 @@ const NewNetwork = decorate([
|
||||
value: mode,
|
||||
}))
|
||||
: [],
|
||||
hostPredicate: ({ networks }, { pool }) => host =>
|
||||
host.$pool === pool.id ||
|
||||
networks.some(
|
||||
({ pool }) => pool !== undefined && pool.id === host.$pool
|
||||
),
|
||||
pifPredicate: (_, { pool }) => pif =>
|
||||
pif.vlan === -1 && pif.$host === (pool && pool.master),
|
||||
pifPredicateSdnController: (_, { pool }) => pif =>
|
||||
@@ -185,6 +194,7 @@ const NewNetwork = decorate([
|
||||
const {
|
||||
bonded,
|
||||
bondMode,
|
||||
networkCenter,
|
||||
isPrivate,
|
||||
description,
|
||||
encapsulation,
|
||||
@@ -221,6 +231,7 @@ const NewNetwork = decorate([
|
||||
encapsulation,
|
||||
encrypted,
|
||||
mtu: mtu !== '' ? +mtu : undefined,
|
||||
preferredCenter: networkCenter,
|
||||
})
|
||||
})()
|
||||
: createNetwork({
|
||||
@@ -267,6 +278,8 @@ const NewNetwork = decorate([
|
||||
const {
|
||||
bonded,
|
||||
bondMode,
|
||||
networkCenter,
|
||||
hostPredicate,
|
||||
isPrivate,
|
||||
description,
|
||||
encapsulation,
|
||||
@@ -369,6 +382,17 @@ const NewNetwork = decorate([
|
||||
<Icon icon='info' /> {_('encryptionWarning')}
|
||||
</em>
|
||||
</div>
|
||||
<label>{_('newNetworkPreferredCenter')}</label>
|
||||
<SelectHost
|
||||
onChange={effects.onChangeCenter}
|
||||
predicate={hostPredicate}
|
||||
value={networkCenter}
|
||||
/>
|
||||
<div>
|
||||
<em>
|
||||
<Icon icon='info' /> {_('preferredCenterTip')}
|
||||
</em>
|
||||
</div>
|
||||
<div className='mt-1'>
|
||||
{state.networks.map(({ pool, pif }, key) => (
|
||||
<div key={key}>
|
||||
|
||||
Reference in New Issue
Block a user