Skip to content

Commit 2934f68

Browse files
committed
test_l3_networks: make the overlap test fail, cover dual-stack and .0
test_05 wrapped self.fail() inside a broad except and could never fail. The dual-stack test deploys an Instance and asserts both address families in host-route form, new tests cover a requested IPv4 address and the subnet's .0 address, and the operator-chosen routed id no longer collides.
1 parent c973f6a commit 2934f68

1 file changed

Lines changed: 82 additions & 40 deletions

File tree

test/integration/smoke/test_l3_networks.py

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
a /32 (or /128) with the shared link-local gateway, delivered via ConfigDrive.
2020
"""
2121

22+
import random
23+
2224
from marvin.cloudstackTestCase import cloudstackTestCase
23-
from marvin.cloudstackException import CloudstackAPIException
2425
from marvin.lib.base import (Account,
2526
Network,
2627
NetworkOffering,
@@ -48,7 +49,6 @@ def setUpClass(cls):
4849
cls.template = get_template(cls.apiclient, cls.zone.id, cls.services["ostype"])
4950

5051
cls._cleanup = []
51-
cls.hypervisor = testClient.getHypervisorInfo()
5252
cls.skip = False
5353

5454
zone = Zone(cls.zone.__dict__)
@@ -136,6 +136,20 @@ def create_l3_network(self, startip="203.0.113.10", endip="203.0.113.50"):
136136
domainid=self.account.domainid
137137
)
138138

139+
def deploy_vm(self, network, **kwargs):
140+
virtual_machine = VirtualMachine.create(
141+
self.apiclient,
142+
self.services["virtual_machine"],
143+
accountid=self.account.name,
144+
domainid=self.account.domainid,
145+
serviceofferingid=self.service_offering.id,
146+
networkids=[network.id],
147+
**kwargs
148+
)
149+
self.cleanup.append(virtual_machine)
150+
self.assertEqual(virtual_machine.state, "Running")
151+
return virtual_machine
152+
139153
@attr(tags=["advanced", "smoke"], required_hardware="false")
140154
def test_01_create_l3_network(self):
141155
""" An L3 network is created like a Shared network: with a subnet. The subnet is an
@@ -162,17 +176,28 @@ def test_01b_create_l3_network_with_operator_specified_id(self):
162176
vlan parameter, so bridge names (brdr-<id>) are plannable before the network
163177
exists. The id must lie outside the physical network's dynamic range. """
164178
services = dict(self.services["l3_network"])
165-
network = Network.create(
166-
self.apiclient,
167-
services,
168-
zoneid=self.zone.id,
169-
networkofferingid=self.network_offering_specifyid.id,
170-
accountid=self.account.name,
171-
domainid=self.account.domainid,
172-
vlan="5928"
173-
)
179+
network = None
180+
# Outside the physical network's dynamic range (5800-5899); retried once in case the
181+
# id is already taken by a pre-existing network or public range in the zone.
182+
for attempt in range(2):
183+
routed_id = random.randint(6000, 9999)
184+
try:
185+
network = Network.create(
186+
self.apiclient,
187+
services,
188+
zoneid=self.zone.id,
189+
networkofferingid=self.network_offering_specifyid.id,
190+
accountid=self.account.name,
191+
domainid=self.account.domainid,
192+
vlan=str(routed_id)
193+
)
194+
break
195+
except Exception as e:
196+
if attempt == 0 and "already" in str(e):
197+
continue
198+
raise
174199
self.cleanup.append(network)
175-
self.assertEqual(network.broadcasturi, "routed://5928",
200+
self.assertEqual(network.broadcasturi, "routed://%d" % routed_id,
176201
"the operator-specified routed id must be carried verbatim, got %s" % network.broadcasturi)
177202

178203
@attr(tags=["advanced", "smoke"], required_hardware="false")
@@ -182,22 +207,41 @@ def test_02_deploy_vm_in_l3_network(self):
182207
network = self.create_l3_network()
183208
self.cleanup.append(network)
184209

185-
virtual_machine = VirtualMachine.create(
186-
self.apiclient,
187-
self.services["virtual_machine"],
188-
accountid=self.account.name,
189-
domainid=self.account.domainid,
190-
serviceofferingid=self.service_offering.id,
191-
networkids=[network.id]
192-
)
193-
self.cleanup.append(virtual_machine)
194-
195-
self.assertEqual(virtual_machine.state, "Running")
210+
virtual_machine = self.deploy_vm(network)
196211
nic = virtual_machine.nic[0]
197212
self.assertEqual(nic.netmask, "255.255.255.255", "an L3 NIC address is a host route (/32)")
198213
self.assertEqual(nic.gateway, "169.254.0.1", "an L3 NIC uses the shared link-local gateway")
199214
self.assertTrue(nic.ipaddress.startswith("203.0.113."), "the address must come from the network's subnet")
200215

216+
@attr(tags=["advanced", "smoke"], required_hardware="false")
217+
def test_02b_deploy_vm_with_requested_ip_in_l3_network(self):
218+
""" As on a Shared network, a user may ask for a specific address from the network's
219+
range at deploy time and the Instance receives exactly that address. """
220+
network = self.create_l3_network()
221+
self.cleanup.append(network)
222+
223+
virtual_machine = self.deploy_vm(network, ipaddress="203.0.113.25")
224+
nic = virtual_machine.nic[0]
225+
self.assertEqual(nic.ipaddress, "203.0.113.25", "the Instance must receive the requested address")
226+
self.assertEqual(nic.netmask, "255.255.255.255", "an L3 NIC address is a host route (/32)")
227+
self.assertEqual(nic.gateway, "169.254.0.1", "an L3 NIC uses the shared link-local gateway")
228+
229+
@attr(tags=["advanced", "smoke"], required_hardware="false")
230+
def test_02c_network_address_is_assignable(self):
231+
""" There is no broadcast domain, so the subnet's first (.0) and last (.255) addresses
232+
are ordinary routable addresses: every Instance is a /32 behind the host's routing
233+
table and nothing broadcasts to them. A range consisting of only the network
234+
address must therefore be accepted, and an Instance must receive it. """
235+
network = self.create_l3_network(startip="203.0.115.0", endip="203.0.115.0")
236+
self.cleanup.append(network)
237+
self.assertEqual(network.cidr, "203.0.115.0/24", "the subnet must derive from the range and netmask")
238+
239+
virtual_machine = self.deploy_vm(network)
240+
nic = virtual_machine.nic[0]
241+
self.assertEqual(nic.ipaddress, "203.0.115.0", "the network address must be assignable to an Instance")
242+
self.assertEqual(nic.netmask, "255.255.255.255", "an L3 NIC address is a host route (/32)")
243+
self.assertEqual(nic.gateway, "169.254.0.1", "an L3 NIC uses the shared link-local gateway")
244+
201245
@attr(tags=["advanced", "smoke"], required_hardware="false")
202246
def test_03_l3_offering_rejects_dhcp(self):
203247
""" DHCP is not supported and not needed on L3 networks: ConfigDrive carries the
@@ -231,12 +275,9 @@ def test_05_l3_subnets_may_not_overlap_zone_wide(self):
231275
network = self.create_l3_network(startip="203.0.113.10", endip="203.0.113.30")
232276
self.cleanup.append(network)
233277

234-
try:
278+
with self.assertRaises(Exception):
235279
overlapping = self.create_l3_network(startip="203.0.113.20", endip="203.0.113.40")
236280
self.cleanup.append(overlapping)
237-
self.fail("creating an L3 network overlapping another must fail")
238-
except (CloudstackAPIException, Exception):
239-
pass
240281

241282
def create_ipv6_only_l3_network(self, ip6cidr="2001:db8:113::/64"):
242283
services = {
@@ -258,7 +299,7 @@ def test_06_create_ipv6_only_l3_network(self):
258299
""" Nothing on an L3 network depends on IPv4 - no DHCP, no password or metadata
259300
service - so IPv4 is optional and an IPv6-only network is valid. Addresses
260301
derive from the subnet and the NIC MAC (EUI-64), so no IPv6 range is needed
261-
either: ip6gateway and ip6cidr alone define the network. """
302+
either: ip6cidr alone defines the network, and a given ip6gateway is ignored. """
262303
network = self.create_ipv6_only_l3_network()
263304
self.cleanup.append(network)
264305

@@ -274,17 +315,7 @@ def test_07_deploy_vm_in_ipv6_only_l3_network(self):
274315
network = self.create_ipv6_only_l3_network()
275316
self.cleanup.append(network)
276317

277-
virtual_machine = VirtualMachine.create(
278-
self.apiclient,
279-
self.services["virtual_machine"],
280-
accountid=self.account.name,
281-
domainid=self.account.domainid,
282-
serviceofferingid=self.service_offering.id,
283-
networkids=[network.id]
284-
)
285-
self.cleanup.append(virtual_machine)
286-
287-
self.assertEqual(virtual_machine.state, "Running")
318+
virtual_machine = self.deploy_vm(network)
288319
nic = virtual_machine.nic[0]
289320
self.assertTrue(getattr(nic, "ip6address", None), "the NIC must carry an IPv6 address")
290321
self.assertEqual(nic.ip6gateway, "fe80::1", "an L3 NIC uses the shared link-local IPv6 gateway")
@@ -320,7 +351,8 @@ def test_08_l3_network_rejects_incomplete_address_family(self):
320351
def test_09_l3_network_ignores_gateways(self):
321352
""" Gateways play no part on an L3 network - the Instance's gateway is always the
322353
shared link-local address - so given gateways are accepted but ignored, and no
323-
address in the subnet is burnt for one. """
354+
address in the subnet is burnt for one. An Instance on this dual-stack network
355+
gets a /32 and a /128, each with its shared link-local gateway. """
324356
services = dict(self.services["l3_network"])
325357
services["gateway"] = "203.0.113.1"
326358
services["ip6gateway"] = "2001:db8:113::1"
@@ -337,6 +369,16 @@ def test_09_l3_network_ignores_gateways(self):
337369
self.assertFalse(getattr(network, "gateway", None), "the given IPv4 gateway must be ignored")
338370
self.assertFalse(getattr(network, "ip6gateway", None), "the given IPv6 gateway must be ignored")
339371

372+
virtual_machine = self.deploy_vm(network)
373+
nic = virtual_machine.nic[0]
374+
self.assertTrue(getattr(nic, "ipaddress", None), "the NIC must carry an IPv4 address")
375+
self.assertEqual(nic.netmask, "255.255.255.255", "an L3 NIC address is a host route (/32)")
376+
self.assertEqual(nic.gateway, "169.254.0.1", "an L3 NIC uses the shared link-local gateway")
377+
self.assertTrue(getattr(nic, "ip6address", None), "the NIC must carry an IPv6 address")
378+
self.assertTrue(getattr(nic, "ip6cidr", "").endswith("/128"),
379+
"an L3 NIC IPv6 address is a host route (/128), got %s" % getattr(nic, "ip6cidr", None))
380+
self.assertEqual(nic.ip6gateway, "fe80::1", "an L3 NIC uses the shared link-local IPv6 gateway")
381+
340382
@attr(tags=["advanced", "smoke"], required_hardware="false")
341383
def test_10_create_l3_network_by_cidr(self):
342384
""" The IPv4 subnet can be given as a CIDR, like IPv6: CloudStack derives the netmask

0 commit comments

Comments
 (0)