diff options
| author | omnom62 <75066712+omnom62@users.noreply.github.com> | 2026-09-01 22:37:23 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-01 15:37:23 +0300 |
| commit | 497022e13c372ed68afe49aeafca66285e2f8001 (patch) | |
| tree | bb01d4dbe45e34a465244e2f89778012ee65a6cf /tests | |
| parent | 61d27bc11d3f64ad4ad66ec42ff5ae83ccd30732 (diff) | |
| download | vyos.vyos-497022e13c372ed68afe49aeafca66285e2f8001.tar.gz vyos.vyos-497022e13c372ed68afe49aeafca66285e2f8001.zip | |
T8323: vyos_nat module (#451)
Add NAT module
Diffstat (limited to 'tests')
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) |
