Skip to content

Commit d0e9f32

Browse files
committed
add traffic rules tests:
- simple VIF rule (add/delete with simple vm.start/destroy cycle) - simple Network rule (add/delete with simple vm.start/destroy cycle) - migrate with simple VIF rule - migrate with Network rule rule - VLAN with simple VIF rule - VLAN with simple Network rule - Tunnel with simple VIF rule - Tunnel with simple Network rule fixtures: - add connected_hosts_with_xo: returns the list of hosts which are already connected to xo-cli - add VLAN fixture: returns a configured VLAN network lib - add VLAN abstraction Signed-off-by: Sebastien Marie <semarie@kapouay.eu.org>
1 parent 09c7ddc commit d0e9f32

File tree

10 files changed

+909
-9
lines changed

10 files changed

+909
-9
lines changed

conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,11 @@ def hosts_with_xo(hosts, registered_xo_cli):
267267
logging.info("<<< Disconnect host %s" % h)
268268
h.xo_server_remove()
269269

270+
@pytest.fixture(scope='session')
271+
def connected_hosts_with_xo(hosts: list[Host], registered_xo_cli):
272+
connected = [h for h in hosts if not h.skip_xo_config and h.xo_server_connected()]
273+
yield connected
274+
270275
@pytest.fixture(scope='session')
271276
def hostA1(hosts):
272277
""" Master of first pool (pool A). """

jobs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,8 @@
470470
"tests/migration/test_host_evacuate.py::TestHostEvacuateWithNetwork",
471471
# not really broken but has complex prerequisites (3 NICs)
472472
"tests/network/test_bond.py::test_bond",
473+
# not really broken but has complex prerequisites (xo-cli + 2 hosts)
474+
"tests/network/test_traffic_rules.py",
473475
# running quicktest on zfsvol generates dangling TAP devices that are hard to
474476
# cleanup. Bug needs to be fixed before enabling quicktest on zfsvol.
475477
"tests/storage/zfsvol/test_zfsvol_sr.py::TestZfsvolVm::test_quicktest",

lib/host.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
from lib.network import Network
3737
from lib.pif import PIF
3838
from lib.sr import SR
39+
from lib.tunnel import Tunnel
3940
from lib.vdi import VDI
41+
from lib.vlan import VLAN
4042
from lib.vm import VM
4143
from lib.xo import xo_cli, xo_object_exists
4244

@@ -808,3 +810,35 @@ def create_network(self, label: str, description: Optional[str] = None) -> Netwo
808810
logging.info(f"New Network: {uuid}")
809811

810812
return Network(self, uuid)
813+
814+
def create_vlan(self, network: Network, pif: PIF, vlan: int) -> VLAN:
815+
args: dict[str, str | bool] = {
816+
'network-uuid': network.uuid,
817+
'pif-uuid': pif.uuid,
818+
'vlan': str(vlan),
819+
}
820+
821+
untagged_pif_uuid = self.xe("vlan-create", args, minimal=True)
822+
uuid = self.xe("pif-param-get", {
823+
"uuid": untagged_pif_uuid,
824+
"param-name": "vlan-master-of",
825+
})
826+
logging.info(f"New VLAN: {uuid} (untagged-pif: {untagged_pif_uuid})")
827+
828+
return VLAN(self, uuid)
829+
830+
def create_tunnel(self, network: Network, pif: PIF, protocol: str) -> Tunnel:
831+
args: dict[str, str | bool] = {
832+
'network-uuid': network.uuid,
833+
'pif-uuid': pif.uuid,
834+
'protocol': protocol,
835+
}
836+
837+
access_pif_uuid = self.xe("tunnel-create", args, minimal=True)
838+
uuid = self.xe("pif-param-get", {
839+
"uuid": access_pif_uuid,
840+
"param-name": "tunnel-access-PIF-of",
841+
})
842+
logging.info(f"New Tunnel: {uuid} (access-pif: {access_pif_uuid})")
843+
844+
return Tunnel(self, uuid)

lib/network.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,8 @@ def managed(self) -> bool:
4949

5050
def MTU(self) -> int:
5151
return int(self.param_get('MTU') or '0')
52+
53+
def bridge(self) -> str:
54+
bridge = self.param_get('bridge')
55+
assert bridge is not None, "network must have a bridge"
56+
return bridge

lib/pif.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ def network_uuid(self) -> str:
5151
assert uuid is not None, "unexpected PIF without network-uuid"
5252
return uuid
5353

54+
def ip_configuration_mode(self) -> str:
55+
mode = self.param_get("IP-configuration-mode")
56+
assert mode
57+
return mode
58+
59+
def vlan(self) -> int | None:
60+
vlan = self.param_get('VLAN')
61+
if vlan is None:
62+
return None
63+
else:
64+
return int(vlan)
65+
5466
def reconfigure_ip(self, mode: str) -> None:
5567
self.host.xe("pif-reconfigure-ip", {
5668
"uuid": self.uuid,

lib/tunnel.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
5+
from lib.common import _param_add, _param_clear, _param_get, _param_remove, _param_set
6+
from lib.pif import PIF
7+
8+
from typing import TYPE_CHECKING
9+
10+
if TYPE_CHECKING:
11+
from lib.host import Host
12+
13+
class Tunnel:
14+
xe_prefix = "tunnel"
15+
16+
def __init__(self, host: Host, uuid: str):
17+
self.host = host
18+
self.uuid = uuid
19+
20+
def param_get(self, param_name, key=None, accept_unknown_key=False):
21+
return _param_get(self.host, Tunnel.xe_prefix, self.uuid, param_name, key, accept_unknown_key)
22+
23+
def param_set(self, param_name, value, key=None):
24+
_param_set(self.host, Tunnel.xe_prefix, self.uuid, param_name, value, key)
25+
26+
def param_add(self, param_name, value, key=None):
27+
_param_add(self.host, Tunnel.xe_prefix, self.uuid, param_name, value, key)
28+
29+
def param_clear(self, param_name):
30+
_param_clear(self.host, Tunnel.xe_prefix, self.uuid, param_name)
31+
32+
def param_remove(self, param_name, key, accept_unknown_key=False):
33+
_param_remove(self.host, Tunnel.xe_prefix, self.uuid, param_name, key, accept_unknown_key)
34+
35+
def destroy(self):
36+
logging.info(f"Destroying Tunnel: {self.uuid}")
37+
self.host.xe('tunnel-destroy', {'uuid': self.uuid})
38+
39+
def access_PIF(self) -> PIF:
40+
uuid = self.param_get("access-PIF")
41+
assert uuid
42+
return PIF(uuid, self.host)
43+
44+
def transport_PIF(self) -> PIF:
45+
uuid = self.param_get("transport-PIF")
46+
assert uuid
47+
return PIF(uuid, self.host)

lib/vif.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22

33
from lib.common import _param_add, _param_clear, _param_get, _param_remove, _param_set
4+
from lib.network import Network
45

56
class VIF:
67
xe_prefix = "vif"
@@ -42,6 +43,11 @@ def mac_address(self) -> str:
4243
assert mac_address is not None, "VIF must have a MAC address"
4344
return mac_address
4445

46+
def network(self) -> Network:
47+
network_uuid = self.param_get('network-uuid')
48+
assert network_uuid is not None, "VIF must have a network-uuid"
49+
return Network(self.vm.host, network_uuid)
50+
4551
def plug(self):
4652
logging.info("Plugging VIF %s on VM %s", self.param_get('device'), self.vm.uuid)
4753
self.vm.host.xe('vif-plug', {'uuid': self.uuid})

lib/vlan.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
5+
from lib.common import _param_add, _param_clear, _param_get, _param_remove, _param_set
6+
from lib.pif import PIF
7+
8+
from typing import TYPE_CHECKING
9+
10+
if TYPE_CHECKING:
11+
from lib.host import Host
12+
13+
class VLAN:
14+
xe_prefix = "vlan"
15+
16+
def __init__(self, host: Host, uuid: str):
17+
self.host = host
18+
self.uuid = uuid
19+
20+
def param_get(self, param_name, key=None, accept_unknown_key=False):
21+
return _param_get(self.host, VLAN.xe_prefix, self.uuid, param_name, key, accept_unknown_key)
22+
23+
def param_set(self, param_name, value, key=None):
24+
_param_set(self.host, VLAN.xe_prefix, self.uuid, param_name, value, key)
25+
26+
def param_add(self, param_name, value, key=None):
27+
_param_add(self.host, VLAN.xe_prefix, self.uuid, param_name, value, key)
28+
29+
def param_clear(self, param_name):
30+
_param_clear(self.host, VLAN.xe_prefix, self.uuid, param_name)
31+
32+
def param_remove(self, param_name, key, accept_unknown_key=False):
33+
_param_remove(self.host, VLAN.xe_prefix, self.uuid, param_name, key, accept_unknown_key)
34+
35+
def destroy(self):
36+
logging.info(f"Destroying VLAN: {self.uuid}")
37+
self.host.xe('vlan-destroy', {'uuid': self.uuid})
38+
39+
def tag(self) -> int:
40+
tag = self.param_get('tag')
41+
assert tag
42+
return int(tag)
43+
44+
def tagged_PIF(self) -> PIF:
45+
uuid = self.param_get("tagged-PIF")
46+
assert uuid
47+
return PIF(uuid, self.host)
48+
49+
def untagged_PIF(self) -> PIF:
50+
uuid = self.param_get("untagged-PIF")
51+
assert uuid
52+
return PIF(uuid, self.host)

tests/network/conftest.py

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
from __future__ import annotations
2+
13
import pytest
24

35
import logging
46

7+
from lib.bond import Bond
58
from lib.host import Host
69
from lib.network import Network
10+
from lib.tunnel import Tunnel
11+
from lib.vlan import VLAN
712

813
from typing import Generator
914

@@ -14,14 +19,7 @@ def host_no_sdn_controller(host: Host):
1419
pytest.skip("This test requires an XCP-ng with no SDN controller")
1520

1621

17-
@pytest.fixture(scope='module')
18-
def empty_network(host: Host) -> Generator[Network, None, None]:
19-
try:
20-
net = host.create_network(label="empty_network for tests")
21-
yield net
22-
finally:
23-
net.destroy()
24-
22+
# ---- Bond ----
2523
@pytest.fixture(params=[])
2624
def bond_devices(request: pytest.FixtureRequest) -> list[str]:
2725
return request.param
@@ -31,7 +29,7 @@ def bond_mode(request: pytest.FixtureRequest) -> str:
3129
return request.param
3230

3331
@pytest.fixture
34-
def bond(host: Host, empty_network: Network, bond_devices: list[str], bond_mode: str):
32+
def bond(host: Host, empty_network: Network, bond_devices: list[str], bond_mode: str) -> Generator[Bond, None, None]:
3533
pifs = []
3634
logging.info(f"bond: resolve PIFs on {host.hostname_or_ip} using \
3735
{[(pif.network_uuid(), pif.param_get('device')) for pif in host.pifs()]}")
@@ -44,3 +42,72 @@ def bond(host: Host, empty_network: Network, bond_devices: list[str], bond_mode:
4442
yield bond
4543
finally:
4644
bond.destroy()
45+
46+
47+
# ---- Network ----
48+
@pytest.fixture(scope='module')
49+
def empty_network(host: Host) -> Generator[Network, None, None]:
50+
try:
51+
net = host.create_network(label="empty_network for tests")
52+
yield net
53+
finally:
54+
net.destroy()
55+
56+
57+
# ---- Tunnel ----
58+
@pytest.fixture(params=["eth0"])
59+
def tunnel_device(request: pytest.FixtureRequest) -> str:
60+
return request.param
61+
62+
@pytest.fixture(params=["gre", "vxlan"])
63+
def tunnel_protocol(request: pytest.FixtureRequest) -> str:
64+
return request.param
65+
66+
@pytest.fixture(params=[False, True])
67+
def tunnel_encryption(request: pytest.FixtureRequest) -> bool:
68+
return request.param
69+
70+
@pytest.fixture
71+
def tunnel(
72+
host: Host, empty_network: Network,
73+
tunnel_device: str, tunnel_protocol: str, tunnel_encryption: bool,
74+
) -> Generator[Tunnel, None, None]:
75+
logging.info(f"tunnel: resolve PIF on {host.hostname_or_ip} using \
76+
{[(pif.network_uuid(), pif.param_get('device')) for pif in host.pifs()]}")
77+
78+
[pif] = host.pifs(device=tunnel_device)
79+
if pif.ip_configuration_mode() == "None":
80+
pytest.skip(f"'tunnel' fixture requires tunnel_device={tunnel_device} to have configured IP")
81+
82+
tunnel = host.create_tunnel(empty_network, pif, tunnel_protocol)
83+
try:
84+
empty_network.param_set('other-config', tunnel_protocol, key='xo:sdn-controller:encapsulation')
85+
empty_network.param_set('other-config', tunnel_encryption, key='xo:sdn-controller:encrypted')
86+
87+
yield tunnel
88+
finally:
89+
empty_network.param_set('other-config', None, key='xo:sdn-controller:encrypted')
90+
empty_network.param_set('other-config', None, key='xo:sdn-controller:encapsulation')
91+
tunnel.destroy()
92+
93+
94+
# ---- VLAN ----
95+
@pytest.fixture(params=["eth0"])
96+
def vlan_device(request: pytest.FixtureRequest) -> str:
97+
return request.param
98+
99+
@pytest.fixture(params=[0])
100+
def vlan_tag(request: pytest.FixtureRequest) -> int:
101+
return request.param
102+
103+
@pytest.fixture
104+
def vlan(host: Host, empty_network: Network, vlan_tag: int, vlan_device: str) -> Generator[VLAN, None, None]:
105+
logging.info(f"vlan: resolve PIF on {host.hostname_or_ip} using \
106+
{[(pif.network_uuid(), pif.param_get('device')) for pif in host.pifs()]}")
107+
108+
[pif] = host.pifs(device=vlan_device)
109+
vlan = host.create_vlan(empty_network, pif, vlan_tag)
110+
try:
111+
yield vlan
112+
finally:
113+
vlan.destroy()

0 commit comments

Comments
 (0)