Skip to content

Commit 05e57f0

Browse files
committed
proxmox_subnet: add unit tests and fix doc
1 parent b30bd19 commit 05e57f0

File tree

3 files changed

+185
-2
lines changed

3 files changed

+185
-2
lines changed

meta/runtime.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ action_groups:
3131
- proxmox_storage
3232
- proxmox_storage_contents_info
3333
- proxmox_storage_info
34+
- proxmox_subnet
3435
- proxmox_tasks_info
3536
- proxmox_template
3637
- proxmox_user

plugins/modules/proxmox_subnet.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
DOCUMENTATION = r"""
1313
module: proxmox_subnet
1414
short_description: Create/Update/Delete subnets from SDN
15+
version_added: "1.4.0"
1516
description:
1617
- Create, update, or delete subnets in Proxmox SDN.
1718
author: 'Jana Hoch <[email protected]> (!UNKNOWN)'
@@ -33,6 +34,16 @@
3334
- If O(state=present) then it will update the subnet if needed.
3435
type: bool
3536
default: True
37+
dhcp_range_update_mode:
38+
description:
39+
- Only applicable for O(state=present) and it will honor and only make changes when O(update=true)
40+
- If set to append and new dhcp_range passed it will just append to existing ranges.
41+
- And If no dhcp_range passed and there are existing ranges it will just ignore existing ranges and only update other params if needed
42+
- If set to overwrite and new dhcp_range passed it will overwrite existing ranges.
43+
- If no dhcp_range passed and there are existing ranges it will delete all dhcp_ranges
44+
type: str
45+
default: append
46+
choices: ['append', 'overwrite']
3647
subnet:
3748
description:
3849
- subnet CIDR.
@@ -190,10 +201,12 @@ def get_ansible_module():
190201
def get_dhcp_range(dhcp_range=None):
191202
if not dhcp_range:
192203
return None
204+
193205
def extract(item):
194206
start = item.get('start-address') or item.get('start')
195207
end = item.get('end-address') or item.get('end')
196208
return f"start-address={start},end-address={end}"
209+
197210
return [extract(x) for x in dhcp_range]
198211

199212

@@ -267,7 +280,7 @@ def update_subnet(self, **subnet_params):
267280
existing_subnets = self.get_subnets(vnet_name)
268281

269282
# Check for subnet params other than dhcp-range
270-
_, subnet_update = compare_list_of_dicts(
283+
x, subnet_update = compare_list_of_dicts(
271284
existing_list=existing_subnets,
272285
new_list=[new_subnet],
273286
uid='id',
@@ -291,7 +304,7 @@ def update_subnet(self, **subnet_params):
291304
if dhcp_range_update_mode == 'append':
292305
if partial_overlap:
293306
self.module.fail_json(
294-
msg=f"There are partially overlapping DHCP ranges. this is not allowed."
307+
msg="There are partially overlapping DHCP ranges. this is not allowed."
295308
)
296309

297310
if len(new_dhcp) > 0:
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (c) 2025, Jana Hoch <[email protected]>
4+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
# SPDX-License-Identifier: GPL-3.0-or-later
6+
7+
from __future__ import absolute_import, division, print_function
8+
9+
__metaclass__ = type
10+
11+
from unittest.mock import patch, Mock
12+
13+
import pytest
14+
15+
proxmoxer = pytest.importorskip("proxmoxer")
16+
17+
from ansible_collections.community.proxmox.plugins.modules import proxmox_subnet
18+
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import (
19+
ModuleTestCase,
20+
set_module_args,
21+
)
22+
import ansible_collections.community.proxmox.plugins.module_utils.proxmox as proxmox_utils
23+
24+
RAW_SUBNETS = [
25+
{
26+
"type": "subnet",
27+
"subnet": "ans1-10.10.2.0-24",
28+
"zone": "ans1",
29+
"snat": 0,
30+
"cidr": "10.10.2.0/24",
31+
"id": "ans1-10.10.2.0-24",
32+
"mask": "24",
33+
"dhcp-range": [
34+
{
35+
"start-address": "10.10.2.5",
36+
"end-address": "10.10.2.25"
37+
},
38+
{
39+
"start-address": "10.10.2.50",
40+
"end-address": "10.10.2.100"
41+
}
42+
],
43+
"digest": "c870dc42a3b5356b6037590e9552cbd5d2334963",
44+
"vnet": "test",
45+
"network": "10.10.2.0"
46+
}
47+
]
48+
49+
50+
def exit_json(*args, **kwargs):
51+
"""function to patch over exit_json; package return data into an exception"""
52+
if 'changed' not in kwargs:
53+
kwargs['changed'] = False
54+
raise SystemExit(kwargs)
55+
56+
57+
def fail_json(*args, **kwargs):
58+
"""function to patch over fail_json; package return data into an exception"""
59+
kwargs['failed'] = True
60+
raise SystemExit(kwargs)
61+
62+
63+
def get_module_args(vnet, subnet, zone, state='present', dhcp_range=None, snat=0, dhcp_range_update_mode='append'):
64+
return {
65+
'api_host': 'host',
66+
'api_user': 'user',
67+
'api_password': 'password',
68+
'vnet': vnet,
69+
'subnet': subnet,
70+
'zone': zone,
71+
'state': state,
72+
'dhcp_range': dhcp_range,
73+
'snat': snat,
74+
'dhcp_range_update_mode': dhcp_range_update_mode
75+
}
76+
77+
78+
class TestProxmoxSubnetModule(ModuleTestCase):
79+
def setUp(self):
80+
super(TestProxmoxSubnetModule, self).setUp()
81+
proxmox_utils.HAS_PROXMOXER = True
82+
self.module = proxmox_subnet
83+
self.fail_json_patcher = patch('ansible.module_utils.basic.AnsibleModule.fail_json',
84+
new=Mock(side_effect=fail_json))
85+
self.exit_json_patcher = patch('ansible.module_utils.basic.AnsibleModule.exit_json', new=exit_json)
86+
87+
self.fail_json_mock = self.fail_json_patcher.start()
88+
self.exit_json_patcher.start()
89+
self.connect_mock = patch(
90+
"ansible_collections.community.proxmox.plugins.module_utils.proxmox.ProxmoxAnsible._connect",
91+
).start()
92+
self.connect_mock.return_value.cluster.return_value.sdn.return_value.vnets.return_value.subnets.return_value.get.return_value = RAW_SUBNETS
93+
94+
def tearDown(self):
95+
self.connect_mock.stop()
96+
self.exit_json_patcher.stop()
97+
self.fail_json_patcher.stop()
98+
super(TestProxmoxSubnetModule, self).tearDown()
99+
100+
def test_subnet_create(self):
101+
# Create new Zone
102+
with pytest.raises(SystemExit) as exc_info:
103+
with set_module_args(get_module_args(vnet='new_vnet',
104+
subnet='10.10.10.0/24',
105+
zone='test_zone')):
106+
self.module.main()
107+
result = exc_info.value.args[0]
108+
assert result["changed"] is True
109+
assert result["msg"] == "Created new subnet 10.10.10.0/24"
110+
assert result['subnet'] == 'test_zone-10.10.10.0-24'
111+
112+
def test_subnet_update(self):
113+
# Normal subnet param (snat) differ
114+
with pytest.raises(SystemExit) as exc_info:
115+
with set_module_args(get_module_args(vnet='test',
116+
subnet='10.10.2.0/24',
117+
zone='ans1',
118+
snat=1)):
119+
self.module.main()
120+
result = exc_info.value.args[0]
121+
assert result["changed"] is True
122+
assert result["msg"] == "Updated subnet ans1-10.10.2.0-24"
123+
assert result['subnet'] == 'ans1-10.10.2.0-24'
124+
125+
# No update needed
126+
with pytest.raises(SystemExit) as exc_info:
127+
with set_module_args(get_module_args(vnet='test',
128+
subnet='10.10.2.0/24',
129+
zone='ans1')):
130+
self.module.main()
131+
result = exc_info.value.args[0]
132+
assert result["changed"] is False
133+
assert result["msg"] == "subnet ans1-10.10.2.0-24 is already present with correct parameters."
134+
assert result['subnet'] == 'ans1-10.10.2.0-24'
135+
136+
# New dhcp_range
137+
with pytest.raises(SystemExit) as exc_info:
138+
with set_module_args(get_module_args(vnet='test',
139+
subnet='10.10.2.0/24',
140+
zone='ans1',
141+
dhcp_range=[{'start': '10.10.2.150', 'end': '10.10.2.200'}])):
142+
self.module.main()
143+
result = exc_info.value.args[0]
144+
assert result["changed"] is True
145+
assert result["msg"] == "Updated subnet ans1-10.10.2.0-24"
146+
assert result['subnet'] == 'ans1-10.10.2.0-24'
147+
148+
# dhcp_range is partially overlapping and mode is append
149+
with pytest.raises(SystemExit) as exc_info:
150+
with set_module_args(get_module_args(vnet='test',
151+
subnet='10.10.2.0/24',
152+
zone='ans1',
153+
dhcp_range=[{'start': '10.10.2.10', 'end': '10.10.2.20'}])):
154+
self.module.main()
155+
result = exc_info.value.args[0]
156+
assert self.fail_json_mock.called
157+
assert result["failed"] is True
158+
assert result["msg"] == "There are partially overlapping DHCP ranges. this is not allowed."
159+
160+
def test_subnet_absent(self):
161+
with pytest.raises(SystemExit) as exc_info:
162+
with set_module_args(get_module_args(vnet='test',
163+
subnet='10.10.2.0/24',
164+
zone='ans1', state='absent')):
165+
self.module.main()
166+
result = exc_info.value.args[0]
167+
assert result["changed"] is True
168+
assert result["msg"] == "Deleted subnet ans1-10.10.2.0-24"
169+
assert result['subnet'] == 'ans1-10.10.2.0-24'

0 commit comments

Comments
 (0)