$this->networkCreate($r), 'Creating a network', '']; case 'network.destroy': $this->record('network', $ref); return [0.5, function () use ($ref): string { $n = $this->db['network'][$ref]; if ((array)$n['VIFs'] !== []) { throw new XapiError(['NETWORK_CONTAINS_VIF', ...array_map('strval', (array)$n['VIFs'])]); } if ((array)$n['PIFs'] !== []) { throw new XapiError(['NETWORK_CONTAINS_PIF', ...array_map('strval', (array)$n['PIFs'])]); } $this->delete('network', $ref); return ''; }, 'Removing a network', $ref]; case 'network.set_MTU': $this->record('network', $ref); $mtu = (int)($p[1] ?? 0); return [0.3, function () use ($ref, $mtu): string { if ($mtu < 68 || $mtu > 9216) { throw new XapiError(['INVALID_VALUE', 'MTU', (string)$mtu]); } $this->set('network', $ref, ['MTU' => $mtu]); foreach ((array)$this->db['network'][$ref]['PIFs'] as $pif) { $this->set('pif', (string)$pif, ['MTU' => $mtu]); } return ''; }, 'Changing the MTU', $ref]; case 'pool.create_VLAN': $device = $ref; $network = (string)($p[1] ?? ''); $vlan = (int)($p[2] ?? -1); $this->record('network', $network); return [2.0, function () use ($device, $network, $vlan): array { if ($vlan < 0 || $vlan > 4094) { throw new XapiError(['VLAN_TAG_INVALID', (string)$vlan]); } $made = []; foreach ($this->db['host'] as $host => $h) { $nic = null; foreach ((array)$h['PIFs'] as $pif) { $r = $this->db['pif'][$pif]; if ($r['device'] === $device && (int)$r['VLAN'] === $vlan) { throw new XapiError(['PIF_VLAN_EXISTS', (string)$pif]); } if ($r['device'] === $device && !empty($r['physical'])) { $nic = $r; } } if ($nic === null) { throw new XapiError(['PIF_DEVICE_NOT_FOUND', $device, $host]); } $made[] = $this->addPif($host, $network, $device, (string)$nic['MAC'], (string)$nic['metrics'], $vlan, false); } return $made; }, 'Creating a VLAN', $network]; case 'pif.destroy': $pif = $this->record('pif', $ref); return [0.5, function () use ($ref, $pif): string { if (!empty($pif['physical']) || (int)$pif['VLAN'] < 0) { throw new XapiError(['PIF_IS_PHYSICAL', $ref]); } $this->removePif($ref); return ''; }, 'Removing a VLAN interface', $ref]; case 'bond.create': $network = $ref; $this->record('network', $network); $members = array_map('strval', (array)($p[1] ?? [])); return [3.0, fn (): string => $this->bondCreate($network, $members, (string)($p[2] ?? ''), (string)($p[3] ?? 'balance-slb'), (array)($p[4] ?? [])), 'Creating a bond', $network]; case 'bond.destroy': $bond = $this->record('bond', $ref); return [2.0, function () use ($ref, $bond): string { foreach ((array)$bond['slaves'] as $pif) { $this->set('pif', (string)$pif, ['bond_slave_of' => 'OpaqueRef:NULL']); } $this->removePif((string)$bond['master']); $this->delete('bond', $ref); return ''; }, 'Removing a bond', $ref]; } return null; } private function networkCreate(array $r): string { $bridges = array_column($this->db['network'], 'bridge'); for ($n = 1; in_array('xapi' . $n, $bridges, true); $n++) { } $mtu = (int)($r['MTU'] ?? 1500) ?: 1500; if ($mtu < 68 || $mtu > 9216) { throw new XapiError(['INVALID_VALUE', 'MTU', (string)$mtu]); } return $this->add('network', ['name_label' => (string)($r['name_label'] ?? ''), 'name_description' => (string)($r['name_description'] ?? ''), 'bridge' => 'xapi' . $n, 'MTU' => $mtu, 'PIFs' => [], 'VIFs' => [], 'other_config' => (array)($r['other_config'] ?? []), 'tags' => (array)($r['tags'] ?? [])]); } private function addPif(string $host, string $network, string $device, string $mac, string $metrics, int $vlan, bool $physical, string $speedName = ''): string { $pif = $this->add('pif', ['device' => $device, 'host' => $host, 'network' => $network, 'MAC' => $mac, 'MTU' => (int)($this->db['network'][$network]['MTU'] ?? 1500), 'VLAN' => $vlan, 'IP' => '', 'netmask' => '', 'gateway' => '', 'DNS' => '', 'ip_configuration_mode' => 'None', 'management' => false, 'currently_attached' => true, 'physical' => $physical, 'metrics' => $metrics, 'bond_master_of' => [], 'bond_slave_of' => 'OpaqueRef:NULL']); $this->append('host', $host, 'PIFs', $pif); $this->append('network', $network, 'PIFs', $pif); return $pif; } private function removePif(string $pif): void { $r = $this->db['pif'][$pif] ?? null; if ($r === null) { return; } $this->removeFrom('host', (string)$r['host'], 'PIFs', $pif); $this->removeFrom('network', (string)$r['network'], 'PIFs', $pif); $this->delete('pif', $pif); } /** @param list $members */ private function bondCreate(string $network, array $members, string $mac, string $mode, array $properties): string { if (count($members) < 2) { throw new XapiError(['PIF_BOND_NEEDS_MORE_MEMBERS']); } if (!in_array($mode, ['balance-slb', 'active-backup', 'lacp'], true)) { throw new XapiError(['INVALID_VALUE', 'mode', $mode]); } $host = null; foreach ($members as $pif) { $r = $this->record('pif', $pif); if ($host !== null && $r['host'] !== $host) { throw new XapiError(['PIF_CANNOT_BOND_CROSS_HOST', $pif]); } $host = (string)$r['host']; if (empty($r['physical']) || (int)$r['VLAN'] >= 0) { throw new XapiError(['PIF_IS_VLAN', $pif]); } if (($r['bond_slave_of'] ?? 'OpaqueRef:NULL') !== 'OpaqueRef:NULL') { throw new XapiError(['PIF_ALREADY_BONDED', $pif]); } } $first = $this->db['pif'][$members[0]]; $n = 0; foreach ((array)$this->db['host'][$host]['PIFs'] as $pif) { $n += str_starts_with((string)$this->db['pif'][$pif]['device'], 'bond') ? 1 : 0; } $master = $this->addPif((string)$host, $network, 'bond' . $n, $mac !== '' ? $mac : (string)$first['MAC'], (string)$first['metrics'], -1, false); $bond = $this->add('bond', ['master' => $master, 'slaves' => $members, 'mode' => $mode, 'properties' => $properties, 'primary_slave' => $members[0], 'links_up' => count($members), 'other_config' => []]); $this->set('pif', $master, ['bond_master_of' => [$bond]]); foreach ($members as $pif) { $this->set('pif', $pif, ['bond_slave_of' => $bond]); } return $bond; } }