chore(xo-server-sdn-controller): complete refactor (#4570)

This commit is contained in:
BenjiReis
2019-10-18 17:04:26 +02:00
committed by Julien Fontanet
parent 05c425698f
commit 4c151ac9aa
6 changed files with 584 additions and 936 deletions

View File

@@ -26,5 +26,6 @@
>
> Rule of thumb: add packages on top.
- xo-server-sdn-controller v0.3.1
- xo-server v5.51.0
- xo-web v5.51.0

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,202 @@
import createLogger from '@xen-orchestra/log'
import { filter, find, forOwn, map, sample } from 'lodash'
// =============================================================================
const log = createLogger('xo:xo-server:sdn-controller:private-network')
// =============================================================================
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?!'
const createPassword = () =>
Array.from({ length: 16 }, _ => sample(CHARS)).join('')
// =============================================================================
export class PrivateNetwork {
constructor(controller, uuid) {
this.controller = controller
this.uuid = uuid
this.networks = {}
}
// ---------------------------------------------------------------------------
async addHost(host) {
if (host.$ref === this.center?.$ref) {
// Nothing to do
return
}
const hostClient = this.controller.ovsdbClients[host.$ref]
if (hostClient === undefined) {
log.error('No OVSDB client found', {
host: host.name_label,
pool: host.$pool.name_label,
})
return
}
const centerClient = this.controller.ovsdbClients[this.center.$ref]
if (centerClient === undefined) {
log.error('No OVSDB client found for star-center', {
privateNetwork: this.uuid,
host: this.center.name_label,
pool: this.center.$pool.name_label,
})
return
}
const network = this.networks[host.$pool.uuid]
const centerNetwork = this.networks[this.center.$pool.uuid]
const otherConfig = network.other_config
const encapsulation =
otherConfig['xo:sdn-controller:encapsulation'] ?? 'gre'
const vni = otherConfig['xo:sdn-controller:vni'] ?? '0'
const password =
otherConfig['xo:sdn-controller:encrypted'] === 'true'
? createPassword()
: undefined
let bridgeName
try {
;[bridgeName] = await Promise.all([
hostClient.addInterfaceAndPort(
network,
centerClient.host.address,
encapsulation,
vni,
password,
this.uuid
),
centerClient.addInterfaceAndPort(
centerNetwork,
hostClient.host.address,
encapsulation,
vni,
password,
this.uuid
),
])
} catch (error) {
log.error('Error while connecting host to private network', {
error,
privateNetwork: this.uuid,
network: network.name_label,
host: host.name_label,
pool: host.$pool.name_label,
})
return
}
log.info('Host added', {
privateNetwork: this.uuid,
network: network.name_label,
host: host.name_label,
pool: host.$pool.name_label,
})
return bridgeName
}
addNetwork(network) {
this.networks[network.$pool.uuid] = network
log.info('Adding network', {
privateNetwork: this.uuid,
network: network.name_label,
pool: network.$pool.name_label,
})
if (this.center === undefined) {
return this.electNewCenter()
}
const hosts = filter(network.$pool.$xapi.objects.all, { $type: 'host' })
return Promise.all(
map(hosts, async host => {
const hostClient = this.controller.ovsdbClients[host.$ref]
const network = this.networks[host.$pool.uuid]
await hostClient.resetForNetwork(network, this.uuid)
await this.addHost(host)
})
)
}
async electNewCenter() {
delete this.center
// TODO: make it random
const hosts = this._getHosts()
for (const host of hosts) {
const pif = find(host.$PIFs, {
network: this.networks[host.$pool.uuid].$ref,
})
if (pif?.currently_attached && host.$metrics.live) {
this.center = host
break
}
}
if (this.center === undefined) {
log.error('No available host to elect new star-center', {
privateNetwork: this.uuid,
})
return
}
await this._reset()
// Recreate star topology
await Promise.all(map(hosts, host => this.addHost(host)))
log.info('New star-center elected', {
center: this.center.name_label,
privateNetwork: this.uuid,
})
}
// ---------------------------------------------------------------------------
getPools() {
const pools = []
forOwn(this.networks, network => {
pools.push(network.$pool)
})
return pools
}
// ---------------------------------------------------------------------------
_reset() {
return Promise.all(
map(this._getHosts(), async host => {
// Clean old ports and interfaces
const hostClient = this.controller.ovsdbClients[host.$ref]
if (hostClient === undefined) {
return
}
const network = this.networks[host.$pool.uuid]
try {
await hostClient.resetForNetwork(network, this.uuid)
} catch (error) {
log.error('Error while resetting private network', {
error,
privateNetwork: this.uuid,
network: network.name_label,
host: host.name_label,
pool: network.$pool.name_label,
})
}
})
)
}
// ---------------------------------------------------------------------------
_getHosts() {
const hosts = []
forOwn(this.networks, network => {
hosts.push(...filter(network.$pool.$xapi.objects.all, { $type: 'host' }))
})
return hosts
}
}

View File

@@ -28,8 +28,7 @@ export class OvsdbClient {
Attributes on created OVS ports (corresponds to a XAPI `PIF` or `VIF`):
- `other_config`:
- `xo:sdn-controller:cross-pool` : UUID of the remote network connected by the tunnel
- `xo:sdn-controller:private-pool-wide`: `true` if created (and managed) by a SDN Controller
- `xo:sdn-controller:private-network-uuid`: UUID of the private network
Attributes on created OVS interfaces:
- `options`:
@@ -67,55 +66,49 @@ export class OvsdbClient {
// ---------------------------------------------------------------------------
async addInterfaceAndPort(
networkUuid,
networkName,
network,
remoteAddress,
encapsulation,
key,
password,
remoteNetwork
privateNetworkUuid
) {
if (
this._adding.find(
elem => elem.id === networkUuid && elem.addr === remoteAddress
elem => elem.id === network.uuid && elem.addr === remoteAddress
) !== undefined
) {
return
}
const adding = { id: networkUuid, addr: remoteAddress }
const adding = { id: network.uuid, addr: remoteAddress }
this._adding.push(adding)
const socket = await this._connect()
const [bridgeUuid, bridgeName] = await this._getBridgeUuidForNetwork(
networkUuid,
networkName,
socket
)
if (bridgeUuid === undefined) {
const bridge = await this._getBridgeForNetwork(network, socket)
if (bridge.uuid === undefined) {
socket.destroy()
this._adding = this._adding.filter(
elem => elem.id !== networkUuid || elem.addr !== remoteAddress
elem => elem.id !== network.uuid || elem.addr !== remoteAddress
)
return
}
const alreadyExist = await this._interfaceAndPortAlreadyExist(
bridgeUuid,
bridgeName,
bridge,
remoteAddress,
socket
)
if (alreadyExist) {
socket.destroy()
this._adding = this._adding.filter(
elem => elem.id !== networkUuid || elem.addr !== remoteAddress
elem => elem.id !== network.uuid || elem.addr !== remoteAddress
)
return bridgeName
return bridge.name
}
const index = ++this._numberOfPortAndInterface
const interfaceName = bridgeName + '_iface' + index
const portName = bridgeName + '_port' + index
const interfaceName = bridge.name + '_iface' + index
const portName = bridge.name + '_port' + index
// Add interface and port to the bridge
const options = { remote_ip: remoteAddress, key: key }
@@ -139,11 +132,9 @@ export class OvsdbClient {
row: {
name: portName,
interfaces: ['set', [['named-uuid', 'new_iface']]],
other_config: toMap(
remoteNetwork !== undefined
? { 'xo:sdn-controller:cross-pool': remoteNetwork }
: { 'xo:sdn-controller:private-pool-wide': 'true' }
),
other_config: toMap({
'xo:sdn-controller:private-network-uuid': privateNetworkUuid,
}),
},
'uuid-name': 'new_port',
}
@@ -151,7 +142,7 @@ export class OvsdbClient {
const mutateBridgeOperation = {
op: 'mutate',
table: 'Bridge',
where: [['_uuid', '==', ['uuid', bridgeUuid]]],
where: [['_uuid', '==', ['uuid', bridge.uuid]]],
mutations: [['ports', 'insert', ['set', [['named-uuid', 'new_port']]]]],
}
const params = [
@@ -163,7 +154,7 @@ export class OvsdbClient {
const jsonObjects = await this._sendOvsdbTransaction(params, socket)
this._adding = this._adding.filter(
elem => elem.id !== networkUuid || elem.addr !== remoteAddress
elem => elem.id !== network.uuid || elem.addr !== remoteAddress
)
if (jsonObjects === undefined) {
socket.destroy()
@@ -189,8 +180,8 @@ export class OvsdbClient {
details,
port: portName,
interface: interfaceName,
bridge: bridgeName,
network: networkName,
bridge: bridge.name,
network: network.name_label,
host: this.host.name_label,
})
socket.destroy()
@@ -200,33 +191,24 @@ export class OvsdbClient {
log.debug('Port and interface added to bridge', {
port: portName,
interface: interfaceName,
bridge: bridgeName,
network: networkName,
bridge: bridge.name,
network: network.name_label,
host: this.host.name_label,
})
socket.destroy()
return bridgeName
return bridge.name
}
async resetForNetwork(
networkUuid,
networkName,
crossPoolOnly,
remoteNetwork
) {
async resetForNetwork(network, privateNetworkUuid) {
const socket = await this._connect()
const [bridgeUuid, bridgeName] = await this._getBridgeUuidForNetwork(
networkUuid,
networkName,
socket
)
if (bridgeUuid === undefined) {
const bridge = await this._getBridgeForNetwork(network, socket)
if (bridge.uuid === undefined) {
socket.destroy()
return
}
// Delete old ports created by a SDN controller
const ports = await this._getBridgePorts(bridgeUuid, bridgeName, socket)
const ports = await this._getBridgePorts(bridge, socket)
if (ports === undefined) {
socket.destroy()
return
@@ -250,15 +232,14 @@ export class OvsdbClient {
// 2019-09-03
// Compatibility code, to be removed in 1 year.
const oldShouldDelete =
(config[0] === 'private_pool_wide' && !crossPoolOnly) ||
(config[0] === 'cross_pool' &&
(remoteNetwork === undefined || remoteNetwork === config[1]))
config[0] === 'private_pool_wide' ||
config[0] === 'cross_pool' ||
config[0] === 'xo:sdn-controller:private-pool-wide' ||
config[0] === 'xo:sdn-controller:cross-pool'
const shouldDelete =
(config[0] === 'xo:sdn-controller:private-pool-wide' &&
!crossPoolOnly) ||
(config[0] === 'xo:sdn-controller:cross-pool' &&
(remoteNetwork === undefined || remoteNetwork === config[1]))
config[0] === 'xo:sdn-controller:private-network-uuid' &&
config[1] === privateNetworkUuid
if (shouldDelete || oldShouldDelete) {
portsToDelete.push(['uuid', portUuid])
@@ -275,7 +256,7 @@ export class OvsdbClient {
const mutateBridgeOperation = {
op: 'mutate',
table: 'Bridge',
where: [['_uuid', '==', ['uuid', bridgeUuid]]],
where: [['_uuid', '==', ['uuid', bridge.uuid]]],
mutations: [['ports', 'delete', ['set', portsToDelete]]],
}
@@ -288,7 +269,7 @@ export class OvsdbClient {
if (jsonObjects[0].error != null) {
log.error('Error while deleting ports from bridge', {
error: jsonObjects[0].error,
bridge: bridgeName,
bridge: bridge.name,
host: this.host.name_label,
})
socket.destroy()
@@ -297,7 +278,7 @@ export class OvsdbClient {
log.debug('Ports deleted from bridge', {
nPorts: jsonObjects[0].result[0].count,
bridge: bridgeName,
bridge: bridge.name,
host: this.host.name_label,
})
socket.destroy()
@@ -335,9 +316,9 @@ export class OvsdbClient {
// ---------------------------------------------------------------------------
async _getBridgeUuidForNetwork(networkUuid, networkName, socket) {
async _getBridgeForNetwork(network, socket) {
const where = [
['external_ids', 'includes', toMap({ 'xs-network-uuids': networkUuid })],
['external_ids', 'includes', toMap({ 'xs-network-uuids': network.uuid })],
]
const selectResult = await this._select(
'Bridge',
@@ -347,25 +328,17 @@ export class OvsdbClient {
)
if (selectResult === undefined) {
log.error('No bridge found for network', {
network: networkName,
network: network.name_label,
host: this.host.name_label,
})
return []
return {}
}
const bridgeUuid = selectResult._uuid[1]
const bridgeName = selectResult.name
return [bridgeUuid, bridgeName]
return { uuid: selectResult._uuid[1], name: selectResult.name }
}
async _interfaceAndPortAlreadyExist(
bridgeUuid,
bridgeName,
remoteAddress,
socket
) {
const ports = await this._getBridgePorts(bridgeUuid, bridgeName, socket)
async _interfaceAndPortAlreadyExist(bridge, remoteAddress, socket) {
const ports = await this._getBridgePorts(bridge, socket)
if (ports === undefined) {
return false
}
@@ -393,8 +366,8 @@ export class OvsdbClient {
return false
}
async _getBridgePorts(bridgeUuid, bridgeName, socket) {
const where = [['_uuid', '==', ['uuid', bridgeUuid]]]
async _getBridgePorts(bridge, socket) {
const where = [['_uuid', '==', ['uuid', bridge.uuid]]]
const selectResult = await this._select('Bridge', ['ports'], where, socket)
if (selectResult === undefined) {
return

View File

@@ -1673,8 +1673,6 @@ export const createBondedNetwork = params =>
_call('network.createBonded', params)
export const createPrivateNetwork = params =>
_call('sdnController.createPrivateNetwork', params)
export const createCrossPoolPrivateNetwork = params =>
_call('sdnController.createCrossPoolPrivateNetwork', params)
export const deleteNetwork = network =>
confirm({

View File

@@ -10,7 +10,6 @@ import Wizard, { Section } from 'wizard'
import { addSubscriptions, connectStore } from 'utils'
import {
createBondedNetwork,
createCrossPoolPrivateNetwork,
createNetwork,
createPrivateNetwork,
getBondModes,
@@ -203,33 +202,23 @@ const NewNetwork = decorate([
pool: pool.id,
})
: isPrivate
? networks.length > 0
? (() => {
const poolIds = [pool.id]
const pifIds = [pif.id]
for (const network of networks) {
poolIds.push(network.pool.id)
pifIds.push(network.pif.id)
}
return createCrossPoolPrivateNetwork({
xoPoolIds: poolIds,
networkName: name,
networkDescription: description,
encapsulation: encapsulation,
xoPifIds: pifIds,
encrypted,
mtu: mtu !== '' ? +mtu : undefined,
})
})()
: createPrivateNetwork({
poolId: pool.id,
networkName: name,
networkDescription: description,
encapsulation: encapsulation,
pifId: pif.id,
? (() => {
const poolIds = [pool.id]
const pifIds = [pif.id]
for (const network of networks) {
poolIds.push(network.pool.id)
pifIds.push(network.pif.id)
}
return createPrivateNetwork({
poolIds,
pifIds,
name,
description,
encapsulation,
encrypted,
mtu: mtu !== '' ? +mtu : undefined,
})
})()
: createNetwork({
description,
mtu,