inmanta-module-openstack 5.0.0__tar.gz → 5.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/PKG-INFO +1 -1
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_module_openstack.egg-info/PKG-INFO +1 -1
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/network/network.py +57 -2
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/setup.cfg +1 -1
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/setup.cfg +1 -1
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/tests/test_neutron.py +133 -12
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/MANIFEST.in +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/README.md +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_module_openstack.egg-info/SOURCES.txt +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_module_openstack.egg-info/dependency_links.txt +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_module_openstack.egg-info/not-zip-safe +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_module_openstack.egg-info/requires.txt +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_module_openstack.egg-info/top_level.txt +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/__init__.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/common/__init__.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/common/base.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/common/deps.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/compute/__init__.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/compute/flavor.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/compute/host_port.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/compute/virtual_machine.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/identity/__init__.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/identity/group.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/identity/group_role.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/identity/keystone_base.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/identity/project.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/identity/role.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/identity/user.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/image/__init__.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/image/image.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/model/_init.cf +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/network/__init__.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/network/floating_ip.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/network/qos_policy.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/network/router.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/network/router_port.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/network/security_group.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/network/subnet.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/network/subnet_v6.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/quota/__init__.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/inmanta_plugins/openstack/quota/quota.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/pyproject.toml +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/tests/test_dependency_handling.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/tests/test_examples.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/tests/test_flavor.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/tests/test_image.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/tests/test_keystone.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/tests/test_nova.py +0 -0
- {inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/tests/test_quota.py +0 -0
|
@@ -16,6 +16,7 @@ limitations under the License.
|
|
|
16
16
|
Contact: code@inmanta.com
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
+
import time
|
|
19
20
|
from typing import Any
|
|
20
21
|
|
|
21
22
|
from inmanta.agent import handler
|
|
@@ -156,7 +157,7 @@ class NetworkHandler(OpenStackHandler):
|
|
|
156
157
|
if "external" in changes:
|
|
157
158
|
self.network.is_router_external = resource.external
|
|
158
159
|
if "port_security_enabled" in changes:
|
|
159
|
-
self.network.
|
|
160
|
+
self.network.is_port_security_enabled = resource.port_security_enabled
|
|
160
161
|
if "mtu" in changes:
|
|
161
162
|
self.network.mtu = resource.mtu
|
|
162
163
|
if "segmentation_id" in changes:
|
|
@@ -172,10 +173,64 @@ class NetworkHandler(OpenStackHandler):
|
|
|
172
173
|
else:
|
|
173
174
|
self.network.qos_policy_id = None
|
|
174
175
|
|
|
175
|
-
|
|
176
|
+
if "segmentation_id" in changes:
|
|
177
|
+
self._update_network_with_rebind(ctx)
|
|
178
|
+
else:
|
|
179
|
+
self.network = self._connection.network.update_network(self.network)
|
|
180
|
+
|
|
176
181
|
ctx.fields_updated(tuple(changes.keys()))
|
|
177
182
|
ctx.set_updated()
|
|
178
183
|
|
|
184
|
+
def _update_network_with_rebind(self, ctx: handler.HandlerContext) -> None:
|
|
185
|
+
"""Update self.network after temporarily unbinding any bound ports.
|
|
186
|
+
|
|
187
|
+
Neutron ML2 rejects provider:segmentation_id updates when any port on
|
|
188
|
+
the network has a binding:vif_type other than 'unbound'/'binding_failed'.
|
|
189
|
+
Clearing binding:host_id forces ML2 to drop the binding; the agent
|
|
190
|
+
re-binds automatically once host_id is restored.
|
|
191
|
+
|
|
192
|
+
TODO: Persist original host_id in port metadata/tags before unbinding,
|
|
193
|
+
and recover possibly leaked bindings on next deploy (refer to #534).
|
|
194
|
+
"""
|
|
195
|
+
saved_host_ids: dict[str, str] = {}
|
|
196
|
+
ports = list(self._connection.network.ports(network_id=self.network.id))
|
|
197
|
+
for port in ports:
|
|
198
|
+
if port.binding_vif_type not in ("unbound", "binding_failed"):
|
|
199
|
+
saved_host_ids[port.id] = port.binding_host_id or ""
|
|
200
|
+
self._connection.network.update_port(port.id, binding_host_id="")
|
|
201
|
+
ctx.debug(
|
|
202
|
+
"Cleared binding:host_id on port %(port_id)s "
|
|
203
|
+
"(was %(host)r) to allow segmentation_id update",
|
|
204
|
+
port_id=port.id,
|
|
205
|
+
host=saved_host_ids[port.id],
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
if saved_host_ids:
|
|
210
|
+
deadline = time.monotonic() + 60
|
|
211
|
+
pending = set(saved_host_ids)
|
|
212
|
+
while pending and time.monotonic() < deadline:
|
|
213
|
+
time.sleep(1)
|
|
214
|
+
for port_id in list(pending):
|
|
215
|
+
port = self._connection.network.get_port(port_id)
|
|
216
|
+
if port.binding_vif_type in ("unbound", "binding_failed"):
|
|
217
|
+
pending.discard(port_id)
|
|
218
|
+
if pending:
|
|
219
|
+
raise TimeoutError(
|
|
220
|
+
f"Timed out waiting for ports to unbind before "
|
|
221
|
+
f"segmentation_id update. Ports still bound: {list(pending)}"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
self.network = self._connection.network.update_network(self.network)
|
|
225
|
+
finally:
|
|
226
|
+
for port_id, host_id in saved_host_ids.items():
|
|
227
|
+
self._connection.network.update_port(port_id, binding_host_id=host_id)
|
|
228
|
+
ctx.debug(
|
|
229
|
+
"Restored binding:host_id on port %(port_id)s to %(host)r",
|
|
230
|
+
port_id=port_id,
|
|
231
|
+
host=host_id,
|
|
232
|
+
)
|
|
233
|
+
|
|
179
234
|
def _set_facts(self, ctx):
|
|
180
235
|
ctx.set_fact("network_id", self.network.id, expires=False)
|
|
181
236
|
ctx.set_fact("status", self.network.status, expires=True)
|
|
@@ -25,6 +25,58 @@ import pytest
|
|
|
25
25
|
from conftest import assert_dryrun_deploy_dryrun
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def test_network_update(project, os_tester):
|
|
29
|
+
|
|
30
|
+
def get_model(port_security_enabled: bool):
|
|
31
|
+
return f"""
|
|
32
|
+
import unittest
|
|
33
|
+
project = openstack::Project(
|
|
34
|
+
provider=provider,
|
|
35
|
+
name=provider.project_name,
|
|
36
|
+
description="",
|
|
37
|
+
enabled=true,
|
|
38
|
+
managed=false
|
|
39
|
+
)
|
|
40
|
+
openstack::Network(
|
|
41
|
+
provider=provider,
|
|
42
|
+
name="test_net",
|
|
43
|
+
project=project,
|
|
44
|
+
external=true,
|
|
45
|
+
mtu=1000,
|
|
46
|
+
port_security_enabled={str(port_security_enabled).lower()},
|
|
47
|
+
)
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
network_name = "test_net"
|
|
51
|
+
try:
|
|
52
|
+
# Create network
|
|
53
|
+
os_tester.compile_with_provider(get_model(port_security_enabled=True))
|
|
54
|
+
|
|
55
|
+
assert project.dryrun_resource("openstack::Network", name=network_name)
|
|
56
|
+
project.deploy_resource("openstack::Network", name=network_name)
|
|
57
|
+
assert not project.dryrun_resource("openstack::Network", name=network_name)
|
|
58
|
+
|
|
59
|
+
networks = list(os_tester.connection.network.networks(name=network_name))
|
|
60
|
+
assert len(networks) == 1
|
|
61
|
+
assert networks[0].is_port_security_enabled
|
|
62
|
+
|
|
63
|
+
# Update network
|
|
64
|
+
os_tester.compile_with_provider(get_model(port_security_enabled=False))
|
|
65
|
+
|
|
66
|
+
assert project.dryrun_resource("openstack::Network", name=network_name)
|
|
67
|
+
project.deploy_resource("openstack::Network", name=network_name)
|
|
68
|
+
assert not project.dryrun_resource("openstack::Network", name=network_name)
|
|
69
|
+
|
|
70
|
+
networks = list(os_tester.connection.network.networks(name=network_name))
|
|
71
|
+
assert len(networks) == 1
|
|
72
|
+
assert not networks[0].is_port_security_enabled
|
|
73
|
+
finally:
|
|
74
|
+
# cleanup
|
|
75
|
+
networks = os_tester.connection.network.networks(name=network_name)
|
|
76
|
+
for network in networks:
|
|
77
|
+
os_tester.connection.network.delete_network(network)
|
|
78
|
+
|
|
79
|
+
|
|
28
80
|
def test_net(project, os_tester):
|
|
29
81
|
try:
|
|
30
82
|
os_tester.compile_with_provider("""
|
|
@@ -1316,19 +1368,28 @@ def test_qos_policy(project, os_tester, require_network_extension):
|
|
|
1316
1368
|
@pytest.fixture
|
|
1317
1369
|
def segmentation_id_network(os_tester):
|
|
1318
1370
|
net_name = "test_update_seg_id"
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1371
|
+
subnet_name = "test_update_seg_id_subnet"
|
|
1372
|
+
yield net_name, subnet_name
|
|
1373
|
+
conn = os_tester.connection
|
|
1374
|
+
for subnet in conn.network.subnets(name=subnet_name):
|
|
1375
|
+
conn.network.delete_subnet(subnet, ignore_missing=True)
|
|
1376
|
+
for network in conn.network.networks(name=net_name):
|
|
1377
|
+
conn.network.delete_network(network, ignore_missing=True)
|
|
1323
1378
|
|
|
1324
1379
|
|
|
1325
1380
|
def test_update_segmentation_id(project, os_tester, segmentation_id_network):
|
|
1326
1381
|
"""
|
|
1327
|
-
Test that segmentation_id can be updated on an existing network
|
|
1382
|
+
Test that segmentation_id can be updated on an existing network that has a
|
|
1383
|
+
DHCP-enabled subnet. When the DHCP agent has bound a port, the handler must
|
|
1384
|
+
temporarily clear binding:host_id on those ports so ML2 accepts the update,
|
|
1385
|
+
then restore it so the agent re-binds on the new VLAN.
|
|
1328
1386
|
"""
|
|
1329
|
-
|
|
1387
|
+
import time as _time
|
|
1388
|
+
|
|
1389
|
+
net_name, subnet_name = segmentation_id_network
|
|
1390
|
+
conn = os_tester.connection
|
|
1330
1391
|
|
|
1331
|
-
# Create network with initial segmentation_id
|
|
1392
|
+
# Create network + subnet with initial segmentation_id.
|
|
1332
1393
|
os_tester.compile_with_provider(f"""
|
|
1333
1394
|
import unittest
|
|
1334
1395
|
project = openstack::Project(provider=provider, name=provider.project_name, description="", enabled=true, managed=false)
|
|
@@ -1339,16 +1400,51 @@ def test_update_segmentation_id(project, os_tester, segmentation_id_network):
|
|
|
1339
1400
|
network_type="vlan",
|
|
1340
1401
|
physical_network="physnet1",
|
|
1341
1402
|
segmentation_id=742,
|
|
1403
|
+
)
|
|
1404
|
+
subnet = openstack::Subnet(
|
|
1405
|
+
provider=provider,
|
|
1406
|
+
project=project,
|
|
1407
|
+
network=n,
|
|
1408
|
+
dhcp=true,
|
|
1409
|
+
name="{subnet_name}",
|
|
1410
|
+
network_address="192.168.242.0/24",
|
|
1342
1411
|
)
|
|
1343
1412
|
""")
|
|
1344
1413
|
|
|
1345
1414
|
assert_dryrun_deploy_dryrun(project, "openstack::Network", name=net_name)
|
|
1415
|
+
assert_dryrun_deploy_dryrun(project, "openstack::Subnet", name=subnet_name)
|
|
1346
1416
|
|
|
1347
|
-
networks = list(
|
|
1417
|
+
networks = list(conn.network.networks(name=net_name))
|
|
1348
1418
|
assert len(networks) == 1
|
|
1419
|
+
network_id = networks[0].id
|
|
1349
1420
|
assert networks[0].provider_segmentation_id == 742
|
|
1350
1421
|
|
|
1351
|
-
#
|
|
1422
|
+
# Wait up to 5 minutes for the DHCP agent to fully bind a port: vif_type must be
|
|
1423
|
+
# neither 'unbound' nor 'binding_failed', AND binding_host_id must be set.
|
|
1424
|
+
# Both conditions are required to exercise the handler's ML2 rebind path.
|
|
1425
|
+
# Agent binding can take on the order of minutes depending on the environment.
|
|
1426
|
+
bound_ports_before: dict[str, str] = {}
|
|
1427
|
+
deadline = _time.monotonic() + 300
|
|
1428
|
+
while _time.monotonic() < deadline:
|
|
1429
|
+
for port in conn.network.ports(
|
|
1430
|
+
network_id=network_id, device_owner="network:dhcp"
|
|
1431
|
+
):
|
|
1432
|
+
if (
|
|
1433
|
+
port.binding_vif_type not in ("unbound", "binding_failed")
|
|
1434
|
+
and port.binding_host_id
|
|
1435
|
+
):
|
|
1436
|
+
bound_ports_before[port.id] = port.binding_host_id
|
|
1437
|
+
if bound_ports_before:
|
|
1438
|
+
break
|
|
1439
|
+
_time.sleep(5)
|
|
1440
|
+
|
|
1441
|
+
assert bound_ports_before, (
|
|
1442
|
+
"No fully-bound DHCP port (vif_type != unbound/binding_failed AND "
|
|
1443
|
+
"binding_host_id set) appeared on the network within 5 minutes. "
|
|
1444
|
+
"Cannot verify the ML2 rebind path."
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
# Update segmentation_id — handler must unbind, update, and rebind.
|
|
1352
1448
|
os_tester.compile_with_provider(f"""
|
|
1353
1449
|
import unittest
|
|
1354
1450
|
project = openstack::Project(provider=provider, name=provider.project_name, description="", enabled=true, managed=false)
|
|
@@ -1359,15 +1455,31 @@ def test_update_segmentation_id(project, os_tester, segmentation_id_network):
|
|
|
1359
1455
|
network_type="vlan",
|
|
1360
1456
|
physical_network="physnet1",
|
|
1361
1457
|
segmentation_id=3817,
|
|
1458
|
+
)
|
|
1459
|
+
subnet = openstack::Subnet(
|
|
1460
|
+
provider=provider,
|
|
1461
|
+
project=project,
|
|
1462
|
+
network=n,
|
|
1463
|
+
dhcp=true,
|
|
1464
|
+
name="{subnet_name}",
|
|
1465
|
+
network_address="192.168.242.0/24",
|
|
1362
1466
|
)
|
|
1363
1467
|
""")
|
|
1364
1468
|
|
|
1365
1469
|
assert_dryrun_deploy_dryrun(project, "openstack::Network", name=net_name)
|
|
1366
1470
|
|
|
1367
|
-
networks = list(
|
|
1471
|
+
networks = list(conn.network.networks(name=net_name))
|
|
1368
1472
|
assert len(networks) == 1
|
|
1369
1473
|
assert networks[0].provider_segmentation_id == 3817
|
|
1370
1474
|
|
|
1475
|
+
# Verify binding:host_id was restored on every port that was bound before.
|
|
1476
|
+
for port_id, expected_host in bound_ports_before.items():
|
|
1477
|
+
port = conn.network.get_port(port_id)
|
|
1478
|
+
assert port.binding_host_id == expected_host, (
|
|
1479
|
+
f"Port {port_id} binding_host_id was not restored: "
|
|
1480
|
+
f"expected {expected_host!r}, got {port.binding_host_id!r}"
|
|
1481
|
+
)
|
|
1482
|
+
|
|
1371
1483
|
# Cleanup
|
|
1372
1484
|
os_tester.compile_with_provider(f"""
|
|
1373
1485
|
import unittest
|
|
@@ -1377,10 +1489,19 @@ def test_update_segmentation_id(project, os_tester, segmentation_id_network):
|
|
|
1377
1489
|
name="{net_name}",
|
|
1378
1490
|
project=project,
|
|
1379
1491
|
purged=true,
|
|
1492
|
+
)
|
|
1493
|
+
subnet = openstack::Subnet(
|
|
1494
|
+
provider=provider,
|
|
1495
|
+
project=project,
|
|
1496
|
+
network=n,
|
|
1497
|
+
dhcp=true,
|
|
1498
|
+
name="{subnet_name}",
|
|
1499
|
+
network_address="192.168.242.0/24",
|
|
1500
|
+
purged=true,
|
|
1380
1501
|
)
|
|
1381
1502
|
""")
|
|
1382
1503
|
|
|
1504
|
+
assert_dryrun_deploy_dryrun(project, "openstack::Subnet", name=subnet_name)
|
|
1383
1505
|
assert_dryrun_deploy_dryrun(project, "openstack::Network", name=net_name)
|
|
1384
1506
|
|
|
1385
|
-
|
|
1386
|
-
assert len(networks) == 0
|
|
1507
|
+
assert len(list(conn.network.networks(name=net_name))) == 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{inmanta_module_openstack-5.0.0 → inmanta_module_openstack-5.0.1}/tests/test_dependency_handling.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|