summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authoromnom62 <75066712+omnom62@users.noreply.github.com>2026-09-01 22:37:23 +1000
committerGitHub <noreply@github.com>2026-09-01 15:37:23 +0300
commit497022e13c372ed68afe49aeafca66285e2f8001 (patch)
treebb01d4dbe45e34a465244e2f89778012ee65a6cf /tests
parent61d27bc11d3f64ad4ad66ec42ff5ae83ccd30732 (diff)
downloadvyos.vyos-497022e13c372ed68afe49aeafca66285e2f8001.tar.gz
vyos.vyos-497022e13c372ed68afe49aeafca66285e2f8001.zip
T8323: vyos_nat module (#451)
Add NAT module
Diffstat (limited to 'tests')
-rw-r--r--tests/integration/targets/vyos_nat/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_nat/tasks/cli.yaml20
-rw-r--r--tests/integration/targets/vyos_nat/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/_parsed.cfg17
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/_populate.yaml8
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/_remove_config.yaml10
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/deleted.yaml34
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/deleted_full_match.yaml70
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/empty_config.yaml60
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/gathered.yaml22
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/merged.yaml110
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/merged_load_balance.yaml107
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/overridden.yaml45
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/parsed.yaml16
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/rendered.yaml32
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/replaced.yaml45
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/replaced_cgnat_pool.yaml77
-rw-r--r--tests/integration/targets/vyos_nat/tests/cli/rtt.yaml70
-rw-r--r--tests/integration/targets/vyos_nat/vars/main.yaml172
-rw-r--r--tests/sanity/ignore-2.15.txt1
-rw-r--r--tests/sanity/ignore-2.16.txt1
-rw-r--r--tests/sanity/ignore-2.17.txt1
-rw-r--r--tests/sanity/ignore-2.18.txt1
-rw-r--r--tests/sanity/ignore-2.19.txt1
-rw-r--r--tests/sanity/ignore-2.20.txt1
-rw-r--r--tests/sanity/ignore-2.21.txt1
-rw-r--r--tests/sanity/ignore-2.22.txt1
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_nat_config.cfg61
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_nat.py839
29 files changed, 1831 insertions, 0 deletions
diff --git a/tests/integration/targets/vyos_nat/defaults/main.yaml b/tests/integration/targets/vyos_nat/defaults/main.yaml
new file mode 100644
index 00000000..164afead
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_nat/tasks/cli.yaml b/tests/integration/targets/vyos_nat/tasks/cli.yaml
new file mode 100644
index 00000000..a71206c0
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tasks/cli.yaml
@@ -0,0 +1,20 @@
+---
+- name: Collect all cli test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/cli"
+ patterns: "{{ testcase }}\\.yaml$"
+ use_regex: true
+ delegate_to: localhost
+ register: test_cases
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=ansible.netcommon.network_cli)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.network_cli
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_nat/tasks/main.yaml b/tests/integration/targets/vyos_nat/tasks/main.yaml
new file mode 100644
index 00000000..e6378581
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run CLI tests
+ ansible.builtin.include_tasks: cli.yaml
+ tags:
+ - network_cli
diff --git a/tests/integration/targets/vyos_nat/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_nat/tests/cli/_parsed.cfg
new file mode 100644
index 00000000..68375366
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/_parsed.cfg
@@ -0,0 +1,17 @@
+set nat destination rule 100 description 'Web server NAT'
+set nat destination rule 100 destination address '198.51.100.10'
+set nat destination rule 100 destination port '80'
+set nat destination rule 100 inbound-interface name 'eth2'
+set nat destination rule 100 log
+set nat destination rule 100 protocol 'tcp'
+set nat destination rule 100 translation address '192.168.1.10'
+set nat destination rule 100 translation port '8080'
+set nat source rule 200 description 'Outbound NAT'
+set nat source rule 200 destination address '192.168.10.100'
+set nat source rule 200 destination port '8083'
+set nat source rule 200 disable
+set nat source rule 200 exclude
+set nat source rule 200 log
+set nat source rule 200 protocol 'tcp'
+set nat source rule 200 translation address 'masquerade'
+set nat source rule 200 translation port '443'
diff --git a/tests/integration/targets/vyos_nat/tests/cli/_populate.yaml b/tests/integration/targets/vyos_nat/tests/cli/_populate.yaml
new file mode 100644
index 00000000..a3715be9
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/_populate.yaml
@@ -0,0 +1,8 @@
+---
+- ansible.builtin.include_tasks: _remove_config.yaml
+
+- name: Setup NAT configuration
+ vyos.vyos.vyos_config:
+ lines: "{{ populate_config }}"
+ vars:
+ ansible_connection: ansible.netcommon.network_cli
diff --git a/tests/integration/targets/vyos_nat/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_nat/tests/cli/_remove_config.yaml
new file mode 100644
index 00000000..3618bf40
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/_remove_config.yaml
@@ -0,0 +1,10 @@
+---
+- name: Remove NAT configuration
+ vyos.vyos.vyos_config:
+ lines:
+ - delete nat
+ - delete nat64
+ - delete nat66
+ ignore_errors: true
+ vars:
+ ansible_connection: ansible.netcommon.network_cli
diff --git a/tests/integration/targets/vyos_nat/tests/cli/deleted.yaml b/tests/integration/targets/vyos_nat/tests/cli/deleted.yaml
new file mode 100644
index 00000000..c08d6b1c
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/deleted.yaml
@@ -0,0 +1,34 @@
+---
+- debug:
+ msg: START vyos_nat deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Delete all NAT configuration
+ register: result
+ vyos.vyos.vyos_nat: &id001
+ config:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands|symmetric_difference(deleted.commands) == []
+
+ - name: Assert that the after dicts were correctly generated
+ assert:
+ that:
+ - result.after|symmetric_difference(deleted.after) == []
+
+ - name: Delete the existing configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/deleted_full_match.yaml b/tests/integration/targets/vyos_nat/tests/cli/deleted_full_match.yaml
new file mode 100644
index 00000000..43a8124f
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/deleted_full_match.yaml
@@ -0,0 +1,70 @@
+---
+- debug:
+ msg: START vyos_nat deleted full-match integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Delete destination rule 100 with full matching config
+ register: result
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Web server NAT
+ protocol: tcp
+ log: true
+ inbound_interface:
+ name: eth2
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands == ["delete nat destination rule 100"]
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - name: Assert rule 100 is actually gone from the device
+ assert:
+ that:
+ - ansible_facts['network_resources']['nat'].nat.destination is not defined or
+ ansible_facts['network_resources']['nat'].nat.destination.rule | default([]) | selectattr('id', 'equalto', 100) | list | length == 0
+
+ - name: Delete the same configuration again (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Web server NAT
+ protocol: tcp
+ log: true
+ inbound_interface:
+ name: eth2
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: deleted
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_nat/tests/cli/empty_config.yaml
new file mode 100644
index 00000000..39b57fee
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/empty_config.yaml
@@ -0,0 +1,60 @@
+---
+- debug:
+ msg: START vyos_nat empty_config integration tests on connection={{ ansible_connection }}
+
+- name: Merged with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_nat:
+ config:
+ state: merged
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state merged'
+
+- name: Replaced with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_nat:
+ config:
+ state: replaced
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state replaced'
+
+- name: Overridden with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_nat:
+ config:
+ state: overridden
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state overridden'
+
+- name: Parsed with empty running_config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_nat:
+ running_config:
+ state: parsed
+
+- assert:
+ that:
+ - result.msg == 'value of running_config parameter must not be empty for state parsed'
+
+- name: Rendered with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_nat:
+ config:
+ state: rendered
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state rendered'
+
+- include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/gathered.yaml b/tests/integration/targets/vyos_nat/tests/cli/gathered.yaml
new file mode 100644
index 00000000..a91410b8
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/gathered.yaml
@@ -0,0 +1,22 @@
+---
+- debug:
+ msg: START vyos_nat gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Gather NAT config from the device
+ register: result
+ vyos.vyos.vyos_nat:
+ state: gathered
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.gathered|symmetric_difference(ansible_facts['network_resources']['nat']) == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/merged.yaml b/tests/integration/targets/vyos_nat/tests/cli/merged.yaml
new file mode 100644
index 00000000..73184fb6
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/merged.yaml
@@ -0,0 +1,110 @@
+---
+- debug:
+ msg: START vyos_nat merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Merge new NAT rule with existing configuration
+ register: result
+ vyos.vyos.vyos_nat: &id001
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 101
+ description: New DNAT rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.11
+ port: "443"
+ translation:
+ address: 192.168.1.11
+ port: "8443"
+ state: merged
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands|symmetric_difference(merged.commands) == []
+ - result.after|symmetric_difference(ansible_facts['network_resources']['nat']) == []
+
+ - name: Assert that before dicts were correctly generated
+ assert:
+ that:
+ - merged['before'] | symmetric_difference(result['before']) | length == 0
+
+ - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ - name: Add static rule with plain inbound_interface string
+ register: result
+ vyos.vyos.vyos_nat: &id002
+ config:
+ nat:
+ static:
+ rule:
+ - id: 300
+ description: Static mapping
+ inbound_interface: eth2
+ destination:
+ address: 192.168.100.20
+ translation:
+ address: 192.168.1.20
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+ - "'set nat static rule 300 inbound-interface eth2' in result.commands"
+
+ - name: Add static rule again (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id002
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ - name: Change static rule inbound_interface
+ register: result
+ vyos.vyos.vyos_nat: &id003
+ config:
+ nat:
+ static:
+ rule:
+ - id: 300
+ description: Static mapping
+ inbound_interface: eth3
+ destination:
+ address: 192.168.100.20
+ translation:
+ address: 192.168.1.20
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands == ["set nat static rule 300 inbound-interface eth3"]
+
+ - name: Change static rule inbound_interface again (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id003
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/merged_load_balance.yaml b/tests/integration/targets/vyos_nat/tests/cli/merged_load_balance.yaml
new file mode 100644
index 00000000..04ee5816
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/merged_load_balance.yaml
@@ -0,0 +1,107 @@
+---
+- debug:
+ msg: START vyos_nat load balance integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Add destination rule with load-balance hash and backends
+ register: result
+ vyos.vyos.vyos_nat: &id001
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 105
+ protocol: tcp
+ destination:
+ port: "80"
+ inbound_interface:
+ name: eth0
+ load_balance:
+ hash:
+ - source-address
+ - destination-address
+ backend:
+ - ip: 10.10.10.1
+ weight: 60
+ - ip: 10.10.10.2
+ weight: 40
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+ - "'set nat destination rule 105 load-balance hash source-address' in result.commands"
+ - "'set nat destination rule 105 load-balance hash destination-address' in result.commands"
+ - "'set nat destination rule 105 load-balance backend 10.10.10.1 weight 60' in result.commands"
+ - "'set nat destination rule 105 load-balance backend 10.10.10.2 weight 40' in result.commands"
+
+ - name: Re-apply same load-balance config (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ - name: Change backend weights only (in-place overwrite check)
+ register: result
+ vyos.vyos.vyos_nat: &id002
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 105
+ protocol: tcp
+ destination:
+ port: "80"
+ inbound_interface:
+ name: eth0
+ load_balance:
+ hash:
+ - source-address
+ - destination-address
+ backend:
+ - ip: 10.10.10.1
+ weight: 70
+ - ip: 10.10.10.2
+ weight: 30
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - name: Extract rule 105 from gathered facts
+ set_fact:
+ rule_105: "{{ ansible_facts['network_resources']['nat'].nat.destination.rule | selectattr('id', 'equalto', 105) | first }}"
+
+ - name: Extract backend weights from rule 105
+ set_fact:
+ backend_105: "{{ rule_105.load_balance.backend }}"
+ weight_101: "{{ (rule_105.load_balance.backend | selectattr('ip', 'equalto', '10.10.10.1') | first).weight }}"
+ weight_102: "{{ (rule_105.load_balance.backend | selectattr('ip', 'equalto', '10.10.10.2') | first).weight }}"
+
+ - name: Assert weights updated and no duplicate/stale backend entries
+ assert:
+ that:
+ - backend_105 | length == 2
+ - weight_101 == 70
+ - weight_102 == 30
+
+ - name: Re-apply changed weights again (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id002
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/overridden.yaml b/tests/integration/targets/vyos_nat/tests/cli/overridden.yaml
new file mode 100644
index 00000000..fbb87d72
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/overridden.yaml
@@ -0,0 +1,45 @@
+---
+- debug:
+ msg: START vyos_nat overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Override NAT configuration
+ register: result
+ vyos.vyos.vyos_nat: &id001
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Overridden DNAT rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.20
+ port: "443"
+ translation:
+ address: 192.168.1.20
+ port: "8443"
+ state: overridden
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands|symmetric_difference(overridden.commands) == []
+ - result.after|symmetric_difference(ansible_facts['network_resources']['nat']) == []
+
+ - name: Override the existing configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/parsed.yaml b/tests/integration/targets/vyos_nat/tests/cli/parsed.yaml
new file mode 100644
index 00000000..13a54f42
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/parsed.yaml
@@ -0,0 +1,16 @@
+---
+- debug:
+ msg: START vyos_nat parsed integration tests on connection={{ ansible_connection }}
+
+- name: Parse NAT configuration from file
+ register: result
+ vyos.vyos.vyos_nat:
+ running_config: "{{ lookup('file', '_parsed.cfg') }}"
+ state: parsed
+
+- assert:
+ that:
+ - result.changed == false
+ - result.parsed|symmetric_difference(merged.before) == []
+
+- include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/rendered.yaml b/tests/integration/targets/vyos_nat/tests/cli/rendered.yaml
new file mode 100644
index 00000000..9c7a8ccb
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/rendered.yaml
@@ -0,0 +1,32 @@
+---
+- debug:
+ msg: START vyos_nat rendered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Render NAT configuration without applying to device
+ register: result
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Rendered rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: rendered
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.rendered|symmetric_difference(rendered.commands) == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/replaced.yaml b/tests/integration/targets/vyos_nat/tests/cli/replaced.yaml
new file mode 100644
index 00000000..a327c623
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/replaced.yaml
@@ -0,0 +1,45 @@
+---
+- debug:
+ msg: START vyos_nat replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Replace NAT destination rule 100
+ register: result
+ vyos.vyos.vyos_nat: &id001
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Replaced DNAT rule
+ protocol: udp
+ destination:
+ address: 198.51.100.10
+ port: "53"
+ translation:
+ address: 192.168.1.53
+ port: "53"
+ state: replaced
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands|symmetric_difference(replaced.commands) == []
+ - result.after|symmetric_difference(ansible_facts['network_resources']['nat']) == []
+
+ - name: Replace the provided configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/replaced_cgnat_pool.yaml b/tests/integration/targets/vyos_nat/tests/cli/replaced_cgnat_pool.yaml
new file mode 100644
index 00000000..77fc295b
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/replaced_cgnat_pool.yaml
@@ -0,0 +1,77 @@
+---
+- debug:
+ msg: START vyos_nat replaced cgnat pool integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Establish baseline CGNAT external pool
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ cgnat:
+ log_allocation: true
+ pool:
+ external:
+ - name: ext-pool-1
+ external_port_range: "10000-20000"
+ per_user_limit:
+ port: "200"
+ range:
+ - value: 203.0.113.0/24
+ - value: 203.1.113.1-203.1.113.60
+ seq: "10"
+ internal:
+ - name: int-pool-1
+ range:
+ - 10.0.0.0/24
+ rule:
+ - id: 1
+ source:
+ pool: int-pool-1
+ translation:
+ pool: ext-pool-1
+ state: merged
+
+ - name: Replace CGNAT pool changing only external_port_range
+ register: result
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ cgnat:
+ log_allocation: true
+ pool:
+ external:
+ - name: ext-pool-1
+ external_port_range: "30000-40000"
+ per_user_limit:
+ port: "200"
+ range:
+ - value: 203.0.113.0/24
+ - value: 203.1.113.1-203.1.113.60
+ seq: "10"
+ internal:
+ - name: int-pool-1
+ range:
+ - 10.0.0.0/24
+ rule:
+ - id: 1
+ source:
+ pool: int-pool-1
+ translation:
+ pool: ext-pool-1
+ state: replaced
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - name: Assert all pool fields survived the replace, not just the changed one
+ assert:
+ that:
+ - result.changed == true
+ - ansible_facts['network_resources']['nat'].nat.cgnat.pool.external[0].external_port_range == "30000-40000"
+ - ansible_facts['network_resources']['nat'].nat.cgnat.pool.external[0].per_user_limit.port == "200"
+ - ansible_facts['network_resources']['nat'].nat.cgnat.pool.external[0].range | length == 2
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/rtt.yaml b/tests/integration/targets/vyos_nat/tests/cli/rtt.yaml
new file mode 100644
index 00000000..8ec9397e
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/rtt.yaml
@@ -0,0 +1,70 @@
+---
+- debug:
+ msg: START vyos_nat round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Apply additional NAT rule to establish base config
+ register: baseconfig
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Web server NAT
+ protocol: tcp
+ log: true
+ inbound_interface:
+ name: eth2
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ - id: 101
+ description: RTT base rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.11
+ port: "443"
+ translation:
+ address: 192.168.1.11
+ port: "8443"
+ state: merged
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - name: Modify rule 101 description
+ register: result
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 101
+ description: RTT modified rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.11
+ port: "443"
+ translation:
+ address: 192.168.1.11
+ port: "8443"
+ state: merged
+
+ - name: Revert back to base config using facts round trip
+ register: revert
+ vyos.vyos.vyos_nat:
+ config: "{{ ansible_facts['network_resources']['nat'] }}"
+ state: overridden
+
+ - name: Assert that config was reverted
+ assert:
+ that: baseconfig.after == revert.after
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/vars/main.yaml b/tests/integration/targets/vyos_nat/vars/main.yaml
new file mode 100644
index 00000000..0bae2c03
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/vars/main.yaml
@@ -0,0 +1,172 @@
+---
+populate_config:
+ - set nat destination rule 100 description 'Web server NAT'
+ - set nat destination rule 100 destination address '198.51.100.10'
+ - set nat destination rule 100 destination port '80'
+ - set nat destination rule 100 inbound-interface name 'eth2'
+ - set nat destination rule 100 log
+ - set nat destination rule 100 protocol 'tcp'
+ - set nat destination rule 100 translation address '192.168.1.10'
+ - set nat destination rule 100 translation port '8080'
+ - set nat source rule 200 description 'Outbound NAT'
+ - set nat source rule 200 destination address '192.168.10.100'
+ - set nat source rule 200 destination port '8083'
+ - set nat source rule 200 disable
+ - set nat source rule 200 exclude
+ - set nat source rule 200 log
+ - set nat source rule 200 protocol 'tcp'
+ - set nat source rule 200 translation address 'masquerade'
+ - set nat source rule 200 translation port '443'
+
+merged:
+ before:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Web server NAT
+ protocol: tcp
+ log: true
+ inbound_interface:
+ name: eth2
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ source:
+ rule:
+ - id: 200
+ description: Outbound NAT
+ protocol: tcp
+ log: true
+ disable: true
+ exclude: true
+ destination:
+ address: 192.168.10.100
+ port: "8083"
+ translation:
+ address: masquerade
+ port: "443"
+ commands:
+ - set nat destination rule 101 description 'New DNAT rule'
+ - set nat destination rule 101 protocol tcp
+ - set nat destination rule 101 destination address 198.51.100.11
+ - set nat destination rule 101 destination port 443
+ - set nat destination rule 101 translation address 192.168.1.11
+ - set nat destination rule 101 translation port 8443
+ after:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Web server NAT
+ protocol: tcp
+ log: true
+ inbound_interface:
+ name: eth2
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ - id: 101
+ description: New DNAT rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.11
+ port: "443"
+ translation:
+ address: 192.168.1.11
+ port: "8443"
+ source:
+ rule:
+ - id: 200
+ description: Outbound NAT
+ protocol: tcp
+ log: true
+ disable: true
+ exclude: true
+ destination:
+ address: 192.168.10.100
+ port: "8083"
+ translation:
+ address: masquerade
+ port: "443"
+
+replaced:
+ commands:
+ - delete nat destination rule 100
+ - set nat destination rule 100 description 'Replaced DNAT rule'
+ - set nat destination rule 100 protocol udp
+ - set nat destination rule 100 destination address 198.51.100.10
+ - set nat destination rule 100 destination port 53
+ - set nat destination rule 100 translation address 192.168.1.53
+ - set nat destination rule 100 translation port 53
+ after:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Replaced DNAT rule
+ protocol: udp
+ destination:
+ address: 198.51.100.10
+ port: "53"
+ translation:
+ address: 192.168.1.53
+ port: "53"
+ source:
+ rule:
+ - id: 200
+ description: Outbound NAT
+ protocol: tcp
+ log: true
+ disable: true
+ exclude: true
+ destination:
+ address: 192.168.10.100
+ port: "8083"
+ translation:
+ address: masquerade
+ port: "443"
+
+overridden:
+ commands:
+ - delete nat source
+ - delete nat destination rule 100
+ - set nat destination rule 100 description 'Overridden DNAT rule'
+ - set nat destination rule 100 protocol tcp
+ - set nat destination rule 100 destination address 198.51.100.20
+ - set nat destination rule 100 destination port 443
+ - set nat destination rule 100 translation address 192.168.1.20
+ - set nat destination rule 100 translation port 8443
+ after:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Overridden DNAT rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.20
+ port: "443"
+ translation:
+ address: 192.168.1.20
+ port: "8443"
+
+deleted:
+ commands:
+ - delete nat
+ after: {}
+
+rendered:
+ commands:
+ - set nat destination rule 100 description 'Rendered rule'
+ - set nat destination rule 100 protocol tcp
+ - set nat destination rule 100 destination address 198.51.100.10
+ - set nat destination rule 100 destination port 80
+ - set nat destination rule 100 translation address 192.168.1.10
+ - set nat destination rule 100 translation port 8080
diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.15.txt
+++ b/tests/sanity/ignore-2.15.txt
@@ -21,6 +21,7 @@ plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.16.txt
+++ b/tests/sanity/ignore-2.16.txt
@@ -21,6 +21,7 @@ plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.17.txt
+++ b/tests/sanity/ignore-2.17.txt
@@ -21,6 +21,7 @@ plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.18.txt
+++ b/tests/sanity/ignore-2.18.txt
@@ -21,6 +21,7 @@ plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.19.txt b/tests/sanity/ignore-2.19.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.19.txt
+++ b/tests/sanity/ignore-2.19.txt
@@ -21,6 +21,7 @@ plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.20.txt b/tests/sanity/ignore-2.20.txt
index 255b0a87..a96f7e91 100644
--- a/tests/sanity/ignore-2.20.txt
+++ b/tests/sanity/ignore-2.20.txt
@@ -20,6 +20,7 @@ plugins/action/lldp.py action-plugin-docs
plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.21.txt b/tests/sanity/ignore-2.21.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.21.txt
+++ b/tests/sanity/ignore-2.21.txt
@@ -21,6 +21,7 @@ plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.22.txt b/tests/sanity/ignore-2.22.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.22.txt
+++ b/tests/sanity/ignore-2.22.txt
@@ -21,6 +21,7 @@ plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_nat_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_nat_config.cfg
new file mode 100644
index 00000000..505f5e57
--- /dev/null
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_nat_config.cfg
@@ -0,0 +1,61 @@
+set nat cgnat log-allocation
+set nat cgnat pool external ext-pool-1 external-port-range '10000-20000'
+set nat cgnat pool external ext-pool-1 per-user-limit port '200'
+set nat cgnat pool external ext-pool-1 range 203.0.113.0/24
+set nat cgnat pool external ext-pool-1 range 203.1.113.1-203.1.113.60 seq '10'
+set nat cgnat pool internal int-pool-1 range '10.0.0.0/24'
+set nat cgnat pool internal int-pool-1 range '10.1.0.0/24'
+set nat cgnat rule 1 source pool 'int-pool-1'
+set nat cgnat rule 1 translation pool 'ext-pool-1'
+set nat destination rule 100 description 'Web server NAT'
+set nat destination rule 100 destination address '198.51.100.10'
+set nat destination rule 100 destination port '80'
+set nat destination rule 100 inbound-interface name 'eth2'
+set nat destination rule 100 log
+set nat destination rule 100 protocol 'tcp'
+set nat destination rule 100 translation address '192.168.1.10'
+set nat destination rule 100 translation options address-mapping 'persistent'
+set nat destination rule 100 translation options port-mapping 'random'
+set nat destination rule 100 translation port '8080'
+set nat destination rule 105 protocol tcp
+set nat destination rule 105 destination port '80'
+set nat destination rule 105 inbound-interface name 'eth0'
+set nat destination rule 105 load-balance hash 'source-address'
+set nat destination rule 105 load-balance hash 'destination-address'
+set nat destination rule 105 load-balance backend 10.10.10.1 weight '60'
+set nat destination rule 105 load-balance backend 10.10.10.2 weight '40'
+set nat source rule 200 description 'Outbound NAT'
+set nat source rule 200 destination address '192.168.10.100'
+set nat source rule 200 destination port '8083'
+set nat source rule 200 disable
+set nat source rule 200 exclude
+set nat source rule 200 log
+set nat source rule 200 protocol 'tcp'
+set nat source rule 200 translation address 'masquerade'
+set nat source rule 200 translation port '443'
+set nat static rule 300 description 'Static mapping'
+set nat static rule 300 destination address '192.168.100.20'
+set nat static rule 300 inbound-interface 'eth2'
+set nat static rule 300 log
+set nat static rule 300 translation address '192.168.1.20'
+set nat64 source rule 10 description 'NAT64 example'
+set nat64 source rule 10 disable
+set nat64 source rule 10 match mark '100'
+set nat64 source rule 10 source prefix '2001:db8::/96'
+set nat64 source rule 10 translation pool 1 address '192.168.100.10'
+set nat64 source rule 10 translation pool 1 description 'NAT64 translation pool'
+set nat64 source rule 10 translation pool 1 disable
+set nat64 source rule 10 translation pool 1 port '1-65535'
+set nat64 source rule 10 translation pool 1 protocol udp
+set nat66 destination rule 20 description 'NAT66 DNAT'
+set nat66 destination rule 20 destination address '2001:db8::1'
+set nat66 destination rule 20 inbound-interface name 'eth1'
+set nat66 destination rule 20 protocol 'tcp'
+set nat66 destination rule 20 translation address '2001:db8:1::10'
+set nat66 destination rule 20 translation port '8443'
+set nat66 source rule 30 description 'NAT66 SNAT'
+set nat66 source rule 30 destination prefix '2001:db8::/96'
+set nat66 source rule 30 outbound-interface name 'eth2'
+set nat66 source rule 30 protocol 'tcp'
+set nat66 source rule 30 source prefix '2001:db8:2::/64'
+set nat66 source rule 30 translation address 'masquerade'
diff --git a/tests/unit/modules/network/vyos/test_vyos_nat.py b/tests/unit/modules/network/vyos/test_vyos_nat.py
new file mode 100644
index 00000000..4ef76305
--- /dev/null
+++ b/tests/unit/modules/network/vyos/test_vyos_nat.py
@@ -0,0 +1,839 @@
+# (c) 2024 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from unittest.mock import patch
+
+from ansible_collections.vyos.vyos.plugins.modules import vyos_nat
+from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args
+
+from .vyos_module import TestVyosModule, load_fixture
+
+
+class TestVyosNatModule(TestVyosModule):
+ module = vyos_nat
+
+ def setUp(self):
+ super(TestVyosNatModule, self).setUp()
+
+ self.mock_get_resource_connection_config = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection",
+ )
+ self.get_resource_connection_config = self.mock_get_resource_connection_config.start()
+
+ self.mock_get_resource_connection_facts = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection",
+ )
+ self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start()
+
+ self.mock_execute_show_command = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.nat.nat.NatFacts.get_config",
+ )
+ self.execute_show_command = self.mock_execute_show_command.start()
+ self.maxDiff = None
+
+ def tearDown(self):
+ super(TestVyosNatModule, self).tearDown()
+ self.mock_get_resource_connection_config.stop()
+ self.mock_get_resource_connection_facts.stop()
+ self.mock_execute_show_command.stop()
+
+ def load_fixtures(self, commands=None, filename=None):
+ if filename is None:
+ filename = "vyos_nat_config.cfg"
+
+ def load_from_file(*args, **kwargs):
+ return load_fixture(filename)
+
+ self.execute_show_command.side_effect = load_from_file
+
+ # -------------------------------------------------------------------------
+ # merged
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_merged_idempotent(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ cgnat=dict(
+ log_allocation=True,
+ pool=dict(
+ external=[
+ dict(
+ name="ext-pool-1",
+ external_port_range="10000-20000",
+ per_user_limit=dict(port="200"),
+ range=[
+ dict(value="203.0.113.0/24"),
+ dict(value="203.1.113.1-203.1.113.60", seq="10"),
+ ],
+ ),
+ ],
+ internal=[
+ dict(
+ name="int-pool-1",
+ range=["10.0.0.0/24", "10.1.0.0/24"],
+ ),
+ ],
+ ),
+ rule=[
+ dict(
+ id=1,
+ source=dict(pool="int-pool-1"),
+ translation=dict(pool="ext-pool-1"),
+ ),
+ ],
+ ),
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Web server NAT",
+ protocol="tcp",
+ log=True,
+ inbound_interface=dict(name="eth2"),
+ destination=dict(address="198.51.100.10", port="80"),
+ translation=dict(
+ address="192.168.1.10",
+ port="8080",
+ address_mapping="persistent",
+ port_mapping="random",
+ ),
+ ),
+ dict(
+ id=105,
+ protocol="tcp",
+ destination=dict(port="80"),
+ inbound_interface=dict(name="eth0"),
+ load_balance=dict(
+ hash=["source-address", "destination-address"],
+ backend=[
+ dict(ip="10.10.10.1", weight=60),
+ dict(ip="10.10.10.2", weight=40),
+ ],
+ ),
+ ),
+ ],
+ ),
+ source=dict(
+ rule=[
+ dict(
+ id=200,
+ description="Outbound NAT",
+ protocol="tcp",
+ log=True,
+ exclude=True,
+ disable=True,
+ destination=dict(address="192.168.10.100", port="8083"),
+ translation=dict(address="masquerade", port="443"),
+ ),
+ ],
+ ),
+ static=dict(
+ rule=[
+ dict(
+ id=300,
+ description="Static mapping",
+ inbound_interface="eth2",
+ destination=dict(address="192.168.100.20"),
+ translation=dict(address="192.168.1.20"),
+ log=True,
+ ),
+ ],
+ ),
+ ),
+ nat64=dict(
+ source=dict(
+ rule=[
+ dict(
+ id=10,
+ description="NAT64 example",
+ disable=True,
+ match=dict(mark=100),
+ source=dict(prefix="2001:db8::/96"),
+ translation=dict(
+ pool=[
+ dict(
+ id=1,
+ address="192.168.100.10",
+ description="NAT64 translation pool",
+ disable=True,
+ port="1-65535",
+ protocol="udp",
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ nat66=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=20,
+ description="NAT66 DNAT",
+ protocol="tcp",
+ inbound_interface=dict(name="eth1"),
+ destination=dict(address="2001:db8::1"),
+ translation=dict(address="2001:db8:1::10", port="8443"),
+ ),
+ ],
+ ),
+ source=dict(
+ rule=[
+ dict(
+ id=30,
+ description="NAT66 SNAT",
+ protocol="tcp",
+ destination=dict(prefix="2001:db8::/96"),
+ outbound_interface=dict(name="eth2"),
+ source=dict(prefix="2001:db8:2::/64"),
+ translation=dict(address="masquerade"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_nat_merged_new_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=101,
+ description="New DNAT rule",
+ protocol="tcp",
+ destination=dict(address="198.51.100.11", port="443"),
+ translation=dict(address="192.168.1.11", port="8443"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat destination rule 101 description 'New DNAT rule'",
+ "set nat destination rule 101 protocol tcp",
+ "set nat destination rule 101 destination address 198.51.100.11",
+ "set nat destination rule 101 destination port 443",
+ "set nat destination rule 101 translation address 192.168.1.11",
+ "set nat destination rule 101 translation port 8443",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_update_existing_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Updated DNAT rule",
+ protocol="tcp",
+ inbound_interface=dict(name="eth2"),
+ destination=dict(address="198.51.100.10", port="80"),
+ translation=dict(
+ address="192.168.1.10",
+ port="8080",
+ address_mapping="persistent",
+ port_mapping="random",
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat destination rule 100 description 'Updated DNAT rule'",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_cgnat_new_pool(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ cgnat=dict(
+ pool=dict(
+ external=[
+ dict(
+ name="ext-pool-2",
+ external_port_range="30000-40000",
+ range=[dict(value="203.0.114.0/24")],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat cgnat pool external ext-pool-2 external-port-range 30000-40000",
+ "set nat cgnat pool external ext-pool-2 range 203.0.114.0/24",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_nat66_new_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat66=dict(
+ source=dict(
+ rule=[
+ dict(
+ id=31,
+ description="New NAT66 SNAT",
+ protocol="udp",
+ outbound_interface=dict(name="eth3"),
+ source=dict(prefix="2001:db8:3::/64"),
+ translation=dict(address="masquerade"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat66 source rule 31 description 'New NAT66 SNAT'",
+ "set nat66 source rule 31 protocol udp",
+ "set nat66 source rule 31 outbound-interface name eth3",
+ "set nat66 source rule 31 source prefix 2001:db8:3::/64",
+ "set nat66 source rule 31 translation address masquerade",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_static_inbound_interface_change(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ static=dict(
+ rule=[
+ dict(
+ id=300,
+ description="Static mapping",
+ inbound_interface="eth3",
+ destination=dict(address="192.168.100.20"),
+ translation=dict(address="192.168.1.20"),
+ log=True,
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = ["set nat static rule 300 inbound-interface eth3"]
+ self.execute_module(changed=True, commands=commands)
+
+ # -------------------------------------------------------------------------
+ # deleted
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_deleted_all(self):
+ set_module_args(dict(state="deleted"))
+ commands = [
+ "delete nat",
+ "delete nat64",
+ "delete nat66",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_deleted_specific_rules(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(rule=[dict(id=100)]),
+ source=dict(rule=[dict(id=200)]),
+ ),
+ ),
+ state="deleted",
+ ),
+ )
+ commands = [
+ "delete nat destination rule 100",
+ "delete nat source rule 200",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_deleted_cgnat_pool(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ cgnat=dict(
+ pool=dict(
+ external=[dict(name="ext-pool-1")],
+ internal=[dict(name="int-pool-1")],
+ ),
+ ),
+ ),
+ ),
+ state="deleted",
+ ),
+ )
+ commands = [
+ "delete nat cgnat pool external ext-pool-1",
+ "delete nat cgnat pool internal int-pool-1",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_deleted_nat64_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat64=dict(
+ source=dict(rule=[dict(id=10)]),
+ ),
+ ),
+ state="deleted",
+ ),
+ )
+ commands = ["delete nat64 source rule 10"]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_deleted_nonexistent_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(rule=[dict(id=999)]),
+ ),
+ ),
+ state="deleted",
+ ),
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_nat_deleted_cgnat_rule_full_match(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ cgnat=dict(
+ rule=[
+ dict(
+ id=1,
+ source=dict(pool="int-pool-1"),
+ translation=dict(pool="ext-pool-1"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="deleted",
+ ),
+ )
+ commands = ["delete nat cgnat rule 1"]
+ self.execute_module(changed=True, commands=commands)
+
+ # -------------------------------------------------------------------------
+ # replaced
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_replaced_idempotent(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Web server NAT",
+ protocol="tcp",
+ log=True,
+ inbound_interface=dict(name="eth2"),
+ destination=dict(address="198.51.100.10", port="80"),
+ translation=dict(
+ address="192.168.1.10",
+ port="8080",
+ address_mapping="persistent",
+ port_mapping="random",
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_nat_replaced_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Replaced DNAT rule",
+ protocol="udp",
+ destination=dict(address="198.51.100.10", port="53"),
+ translation=dict(address="192.168.1.53", port="53"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete nat destination rule 100",
+ "set nat destination rule 100 description 'Replaced DNAT rule'",
+ "set nat destination rule 100 protocol udp",
+ "set nat destination rule 100 destination address 198.51.100.10",
+ "set nat destination rule 100 destination port 53",
+ "set nat destination rule 100 translation address 192.168.1.53",
+ "set nat destination rule 100 translation port 53",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_replaced_cgnat_pool_partial_field_change(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ cgnat=dict(
+ pool=dict(
+ external=[
+ dict(
+ name="ext-pool-1",
+ external_port_range="30000-40000",
+ per_user_limit=dict(port="200"),
+ range=[
+ dict(value="203.0.113.0/24"),
+ dict(value="203.1.113.1-203.1.113.60", seq="10"),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete nat cgnat pool external ext-pool-1",
+ "delete nat cgnat log-allocation",
+ "set nat cgnat pool external ext-pool-1 external-port-range 30000-40000",
+ "set nat cgnat pool external ext-pool-1 per-user-limit port 200",
+ "set nat cgnat pool external ext-pool-1 range 203.0.113.0/24",
+ "set nat cgnat pool external ext-pool-1 range 203.1.113.1-203.1.113.60 seq 10",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ # -------------------------------------------------------------------------
+ # overridden
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_overridden_remove_sections(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Overridden web server NAT", # changed
+ protocol="tcp",
+ inbound_interface=dict(name="eth3"), # changed
+ destination=dict(address="198.51.100.10", port="80"),
+ translation=dict(
+ address="192.168.1.10",
+ port="8080",
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="overridden",
+ ),
+ )
+ commands = [
+ "delete nat cgnat",
+ "delete nat source",
+ "delete nat static",
+ "delete nat64",
+ "delete nat66",
+ "delete nat destination rule 100",
+ "delete nat destination rule 105",
+ "set nat destination rule 100 description 'Overridden web server NAT'",
+ "set nat destination rule 100 protocol tcp",
+ "set nat destination rule 100 inbound-interface name eth3",
+ "set nat destination rule 100 destination address 198.51.100.10",
+ "set nat destination rule 100 destination port 80",
+ "set nat destination rule 100 translation address 192.168.1.10",
+ "set nat destination rule 100 translation port 8080",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ # -------------------------------------------------------------------------
+ # rendered
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_rendered(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Rendered rule",
+ protocol="tcp",
+ destination=dict(address="198.51.100.10", port="80"),
+ translation=dict(address="192.168.1.10", port="8080"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="rendered",
+ ),
+ )
+ rendered_cmds = [
+ "set nat destination rule 100 description 'Rendered rule'",
+ "set nat destination rule 100 protocol tcp",
+ "set nat destination rule 100 destination address 198.51.100.10",
+ "set nat destination rule 100 destination port 80",
+ "set nat destination rule 100 translation address 192.168.1.10",
+ "set nat destination rule 100 translation port 8080",
+ ]
+ result = self.execute_module(changed=False)
+ self.assertEqual(sorted(result["rendered"]), sorted(rendered_cmds), result["rendered"])
+
+ # -------------------------------------------------------------------------
+ # parsed
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_parsed(self):
+ parsed_str = (
+ "set nat destination rule 100 description 'Web server NAT'\n"
+ "set nat destination rule 100 destination address '198.51.100.10'\n"
+ "set nat destination rule 100 destination port '80'\n"
+ "set nat destination rule 100 inbound-interface name 'eth2'\n"
+ "set nat destination rule 100 log\n"
+ "set nat destination rule 100 protocol 'tcp'\n"
+ "set nat destination rule 100 translation address '192.168.1.10'\n"
+ "set nat destination rule 100 translation port '8080'"
+ )
+ set_module_args(dict(running_config=parsed_str, state="parsed"))
+ result = self.execute_module(changed=False)
+ parsed_list = {
+ "nat": {
+ "destination": {
+ "rule": [
+ {
+ "id": 100,
+ "description": "Web server NAT",
+ "protocol": "tcp",
+ "log": True,
+ "inbound_interface": {"name": "eth2"},
+ "destination": {"address": "198.51.100.10", "port": "80"},
+ "translation": {"address": "192.168.1.10", "port": "8080"},
+ },
+ ],
+ },
+ },
+ }
+ self.assertEqual(parsed_list, result["parsed"])
+
+ # -------------------------------------------------------------------------
+ # gathered
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_gathered(self):
+ set_module_args(dict(state="gathered"))
+ result = self.execute_module(changed=False)
+ gathered = result["gathered"]
+ self.assertIn("nat", gathered)
+ self.assertIn("nat64", gathered)
+ self.assertIn("nat66", gathered)
+ self.assertEqual(gathered["nat"]["destination"]["rule"][0]["id"], 100)
+ self.assertEqual(gathered["nat64"]["source"]["rule"][0]["id"], 10)
+ self.assertEqual(gathered["nat66"]["destination"]["rule"][0]["id"], 20)
+
+ def test_vyos_nat_merged_load_balance_new_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=110,
+ protocol="tcp",
+ destination=dict(port="443"),
+ load_balance=dict(
+ hash=["random"],
+ backend=[
+ dict(ip="10.20.20.1", weight=100),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat destination rule 110 protocol tcp",
+ "set nat destination rule 110 destination port 443",
+ "set nat destination rule 110 load-balance hash random",
+ "set nat destination rule 110 load-balance backend 10.20.20.1 weight 100",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_load_balance_hash_add(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=105,
+ protocol="tcp",
+ destination=dict(port="80"),
+ inbound_interface=dict(name="eth0"),
+ load_balance=dict(
+ hash=[
+ "source-address",
+ "destination-address",
+ "source-port",
+ ],
+ backend=[
+ dict(ip="10.10.10.1", weight=60),
+ dict(ip="10.10.10.2", weight=40),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = ["set nat destination rule 105 load-balance hash source-port"]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_load_balance_backend_weight_change(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=105,
+ protocol="tcp",
+ destination=dict(port="80"),
+ inbound_interface=dict(name="eth0"),
+ load_balance=dict(
+ hash=["source-address", "destination-address"],
+ backend=[
+ dict(ip="10.10.10.1", weight=70),
+ dict(ip="10.10.10.2", weight=30),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat destination rule 105 load-balance backend 10.10.10.1 weight 70",
+ "set nat destination rule 105 load-balance backend 10.10.10.2 weight 30",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_replaced_load_balance_removes_omitted(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=105,
+ protocol="tcp",
+ destination=dict(port="80"),
+ inbound_interface=dict(name="eth0"),
+ load_balance=dict(
+ hash=["source-address"],
+ backend=[
+ dict(ip="10.10.10.1", weight=100),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete nat destination rule 105",
+ "set nat destination rule 105 protocol tcp",
+ "set nat destination rule 105 destination port 80",
+ "set nat destination rule 105 inbound-interface name eth0",
+ "set nat destination rule 105 load-balance hash source-address",
+ "set nat destination rule 105 load-balance backend 10.10.10.1 weight 100",
+ ]
+ self.execute_module(changed=True, commands=commands)