feat(sdn-controller/new network): choose preferred center (#5000)

Fixes #4991
This commit is contained in:
BenjiReis
2020-05-26 16:56:46 +02:00
committed by GitHub
parent 3e3ce543a8
commit 8d7e95d6e9
6 changed files with 107 additions and 21 deletions

View File

@@ -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

View File

@@ -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),

View File

@@ -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
}
}
}
}

View File

@@ -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?',

View File

@@ -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({

View File

@@ -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}>