From 8d7e95d6e9fdeed691879097f14e2e6b4cbc076a Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Tue, 26 May 2020 16:56:46 +0200 Subject: [PATCH] feat(sdn-controller/new network): choose preferred center (#5000) Fixes #4991 --- CHANGELOG.unreleased.md | 1 + .../xo-server-sdn-controller/src/index.js | 48 ++++++++++++++++--- .../src/private-network/private-network.js | 43 ++++++++++++----- packages/xo-web/src/common/intl/messages.js | 2 + packages/xo-web/src/common/xo/index.js | 8 +++- .../xo-web/src/xo-app/new/network/index.js | 26 +++++++++- 6 files changed, 107 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index aea2a60bf..37eb837a9 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -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 diff --git a/packages/xo-server-sdn-controller/src/index.js b/packages/xo-server-sdn-controller/src/index.js index 96bfac897..caa229e22 100644 --- a/packages/xo-server-sdn-controller/src/index.js +++ b/packages/xo-server-sdn-controller/src/index.js @@ -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), diff --git a/packages/xo-server-sdn-controller/src/private-network/private-network.js b/packages/xo-server-sdn-controller/src/private-network/private-network.js index 1a3145b1e..e07027772 100644 --- a/packages/xo-server-sdn-controller/src/private-network/private-network.js +++ b/packages/xo-server-sdn-controller/src/private-network/private-network.js @@ -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 + } + } + } } diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index de78249a2..36ef20665 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -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?', diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 880ad22bf..1475b9a02 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -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({ diff --git a/packages/xo-web/src/xo-app/new/network/index.js b/packages/xo-web/src/xo-app/new/network/index.js index 3a3de4e83..20f9b3e99 100644 --- a/packages/xo-web/src/xo-app/new/network/index.js +++ b/packages/xo-web/src/xo-app/new/network/index.js @@ -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([ {_('encryptionWarning')} + + +
+ + {_('preferredCenterTip')} + +
{state.networks.map(({ pool, pif }, key) => (